2013-04-04 22 views
13

Chcę zapobiec wielokrotnemu przesyłaniu formularzy za pomocą pliku angular.js. Pytanie jest związane z tym question.zapobiega przesyłaniu wielu formularzy za pomocą metody angular.js - wyłącz przycisk formularza

Gdy użytkownik kliknie przycisk przesyłania formularza, wartość/etykieta przycisku przesyłania powinna zmienić się na "ładowanie ..", status przycisku zostanie ustawiony na wyłączony, a wydarzenie zostanie uruchomione w normalny sposób , prowadząc do wywołania wywołania na serwer. W ten sposób użytkownik może zobaczyć następujący efekt:

  1. Natychmiast: submit zmiany wartości przycisk do „loading ..” i zostaje wyłączona

  2. Jak tylko serwer odpowiada: użytkownik zostanie przedstawiony wynik na żądanie serwera (server reaguje natomiast są obsługiwane bez interwencji) kątową

stworzyłem ten plunk pokazać, co mam na myśli. Mój problem dotyczy tej linii: elm.attr('disabled',true);. To nie tylko wyłącza przycisk, ale także zapobiega propagacji zdarzenia przesyłania. W ten sposób otrzymuję wyłączony przycisk (pożądany wynik), ale formularz nie zostanie przesłany (niepożądany wynik).

Widać zmianę zachowań, jeśli komentarz/znak komentarza tę linię: elm.attr('disabled',true);

Każdy pomysł jak to zmienić?

+0

Możliwy duplikat [Zapobieganie/czynienia z podwójnego kliknięcia przycisku w kątowym] (http: // stackoverflow.com/questions/18130808/preventing-dealing-with-double-button-clicks-in-angular) – mattias

Odpowiedz

10

Spróbuj $ limit czasu (funkcja angularjs)

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

Dzięki, wydaje się, że to wystarczy: http://plnkr.co/edit/pl7d7uCuA4tRbproT5PP?p=preview .. czy możesz mi wytłumaczyć dlaczego!? .. i dlaczego limit czasu wynosi zero? –

+0

Limit czasu pozwala przeglądarce najpierw przesłać, a następnie wyłączyć przycisk. Funkcja w wywołaniu 'setTimeout' jest umieszczana w pętli zdarzeń przeglądarki i wykonywana natychmiast po przesłaniu formularza. Bez tego przycisk jest wyłączany zbyt wcześnie. –

+4

Prawdopodobnie zamiast tego powinienem użyć '$ timeout'? tj. dodaj parametr $ timeout do funkcji dyrektywy, a następnie użyj wartości $ timeout (function() { elm.attr ("wyłączone", true); }, 0); ' –

20

Mam standardowego formularza i po prostu użyć kątowy w front-end, więc jeśli wystarczy, aby zapobiec przycisk jest kliknięty dwukrotnie, podczas gdy serwer odpowiada, możesz użyć tej prostej dyrektywy, która jest wielokrotnego użytku i nie wymaga żadnego kontrolera ani ngModel.

http://plnkr.co/edit/2aZWQSLS8s6EhO5rKnRh?p=preview

app.directive('clickOnce', function($timeout) { 
    return { 
     restrict: 'A', 
     link: function(scope, element, attrs) { 
      var replacementText = attrs.clickOnce; 

      element.bind('click', function() { 
       $timeout(function() { 
        if (replacementText) { 
         element.html(replacementText); 
        } 
        element.attr('disabled', true); 
       }, 0); 
      }); 
     } 
    }; 
}); 

Będzie wyłączyć przycisk i ewentualnie zmienić tekst przycisku. Używać tak:

<button click-once>Button just disables</button> 
<button click-once="Loading...">Text changes and button disables</button> 

W swojej obecnej postaci to będzie działać tylko wtedy, gdy robisz standardowe zgłaszanie formularzy i nie ajax zgłoszenia.

+0

prawie idealnie, ale nie ma problemu pracować z ajaxem. jakiejkolwiek rekomendacji dla przesłanych ajaxów? – JasonS

+0

To jest celowo uproszczone w moim przypadku, który miał być zintegrowany ze standardowym formularzem. Istnieje kilka rozwiązań dla żądania AJAX od ponownego włączenia przycisku odpowiedzi, do ogólnych haków. Na przykład jest tu wpis na blogu: http://blog.codebrag.com/post/57412530001/preventing-duplicated-requests-in-angularjs. Tak jak powiedziałem, celowo wybrałem wersję ze skróconą wersją do standardowego przesyłania formularzy, łatwą do przetestowania, itp. –

+1

@MattByrne to jest świetne i zaimplementowałem to w mojej formie. ale nie pozwala ludziom poprawiać nieprawidłowych pól i ponownie przesyłać.przycisk wysyłania zostanie na stałe wyłączony. jakieś przemyślenia, jak to naprawić? dzięki! – user3527354

1

Skończyło się również na drodze dyrektywy. Następujące jest używane zamiast ng-click i oczekuje, że przekazana funkcja zwróci obietnicę (która jest restytucyjna). Kiedy obietnica zostanie rozwiązana (odpowiedź zostanie zwrócona) - pozwoli na późniejsze przesłanie. Można również zmodyfikować to, aby dodać/usunąć ng-wyłączone.

// copied from ngClick and modified to provide single click only behavior 
// expects function to return promise 
app.directive('srMutexClick', function ($parse) { 
    return { 
    compile: function ($element, attr) { 
     var fn = $parse(attr['srMutexClick']); 
     return function srEventHandler(scope, element) { 
     var submitting = false; 
     element.on('click', function (event) { 
      scope.$apply(function() { 
      if (submitting) { 
       return 
      } 
      submitting = true; 
      // `submitting` is reset when promise is resolved 
      fn(scope, {$event: event}).finally(function() { submitting = false }); 
      }); 
     }); 
     }; 
    } 
    }; 
}); 
6

Alternatywa (elastyczne & proste) Roztwór (inspiration) funkcją owinięcie wokół kodu twierdzą, że ustawia zmienną zakresu. Zobacz live example.

Wykorzystanie w kontrolerze:

$scope.submit = mutexAction($scope, 'sending', submit); 

w widoku:

<form ng-submit="submit()"> 
    ... 
    <button ng-disabled="sending"> 
    {{sending ? "Sending..." : "Send"}} 
    </button> 
</form> 

Funkcja (umieścić go w pracy):

function mutexAction(scope, semaphoreName, action) { 
    return function() { 
    if (scope[semaphoreName]) { 
     // not queuing action, forget it! 
     return; 
    } 
    scope[semaphoreName] = true; 
    action()['finally'](function() { 
     scope[semaphoreName] = false; 
    }); 
    }; 
} 
15

Wystarczy dodać nową właściwość w kontrolerze

$scope.processing = false; 

W swojej metodzie

$scope.processData = function(){ 
    $scope.processing = true; 
    $http.post('').then(function(){ 
     $scope.processing = false; 
    }); 
}); 

w HTML wiążą ng-disabled atrybut do właściwości $ scope.processing wyłączyć przycisk i pokazać projekt, podczas gdy metoda jest przetwarzanie.

+0

Zasadniczo najprostszy i najbardziej elastyczny sposób na zrobienie tego, moim zdaniem –

2

Dodatek do spenthil odpowiedź, wariant w coffeescript + można włączyć przycisk z powrotem, jeśli potrzeba (np gdy walidacja formularza nie powiodło się i chcesz spróbować ponownie)

class ClickOnceDirective 

    constructor: (@$timeout) -> 
     link = (scope, element, attrs) => 

      originalText = element.html() 
      replacementText = attrs.clickOnce 

      element.bind('click', => 
       @$timeout -> 

        if (replacementText) 
         element.html(replacementText) 

        element.attr('disabled', true) 

        # enable back 
        @$timeout -> 
         element.attr('disabled', false) 
         if (replacementText) 
          element.html(originalText) 
        , 500 

       , 0) 

     return { 
     link 
     restrict: 'A' 
     } 

directivesModule.directive 'clickOnce', ['$timeout', ClickOnceDirective] 
1

tutaj jest prosty sposób na robienie podobnych rzeczy za pomocą prostych kontroli logicznych, zmiana tekstu może być również wykonana w podobny sposób.

<button type="submit" class="btn btn-success btn-lg btn-block" ng-disabled="!userForm.$valid || isValid">Submit!</button> 


$scope.isValid = false; 
    $scope.submitForm = function() { 
     $scope.isValid = true; 
     console.log('submit'); 
    } 
0

Oto ogólny sposób, aby zrobić to dla wszystkich żądań AJAX wykorzystujących przechwytywacze $ http. Jeśli masz wszystkich swoich tras zaczynając od REST/api/potem:

 angular.module('yourapp').factory('loadingInterceptor',['$q','$rootScope',function($q,$rootScope) { 
     var apiRe = /^\/api\//; 
    return { 
     request: function(config) { 
      if (config.url.match(apiRe)) { 
       $rootScope.loading = true; 
      } 
      config.headers = config.headers || {}; 

      return config; 
     }, 
     response: function(res) { 
      $rootScope.loading = false; 
      return $q.resolve(res); 
     }, 

     'responseError': function(rejection) { 
      $rootScope.loading = false; 
      return $q.reject(rejection); 
     } 
    }; 
}]); 


angular.module('yourapp').config(['$httpProvider', function($httpProvider) { 
    $httpProvider.interceptors.push('loadingInterceptor'); 
}]); 

Korzystanie przechwytujących nie będzie musiał umieścić $ scope.isLoading w każdym kontrolerze. Minusem jest to, że każdy przycisk z ng-disabled = "loading" zostanie zablokowany podczas żądania.

1

Najprostszym i najczęstszym sposobem robi to polegać na $submitted formy własności obiektu tak:

<form name="formName" ng-submit="submit()"> 
    ... 
    <button type="submit" ng-disabled="formName.$submitted">Submit</button> 
</form>