Sprawdź poprawność pól po opuszczeniu pola przez użytkownika

92

Dzięki AngularJS mogę użyć ng-pristinelub ng-dirtywykryć, czy użytkownik wszedł na pole. Jednak chcę przeprowadzić walidację po stronie klienta dopiero po opuszczeniu przez użytkownika obszaru pola. Dzieje się tak, ponieważ gdy użytkownik wejdzie w pole, takie jak e-mail lub telefon, zawsze otrzyma komunikat o błędzie, dopóki nie zakończy wpisywania pełnego adresu e-mail, a to nie jest optymalne dla użytkownika.

Przykład


AKTUALIZACJA:

Angular jest teraz dostarczany z niestandardowym zdarzeniem rozmycia: https://docs.angularjs.org/api/ng/directive/ngBlur

mcranston18
źródło
3
Wydaje mi się, że jest to coś, co angularjs powinien był wspierać ...
csg,
Może. Wbudowana walidacja formularza działa całkiem dobrze, więc nie wiem, czy chciałbym ją wbudować. W końcu większość przypadków użycia chcę, aby Angular natychmiast sprawdzał poprawność. tj. w polu liczbowym chcę, aby formularz był natychmiast unieważniany, jeśli użytkownik zacznie wpisywać litery.
mcranston18
11
walidacja rozmycia jest powszechna od 15 lat ...
Olivvv

Odpowiedzi:

75

Angular 1.3 ma teraz opcje ng-model-options i możesz ustawić tę opcję { 'updateOn': 'blur'}na przykład, a nawet możesz odrzucić, gdy użycie jest albo zbyt szybkie, albo chcesz zaoszczędzić kilka kosztownych operacji DOM (takich jak pisanie modelu do wielu miejsc DOM i nie chcesz, aby cykl podsumowania $ występował przy każdym naciśnięciu klawisza)

https://docs.angularjs.org/guide/forms#custom-triggers i https://docs.angularjs.org/guide/forms#non-immediate-debounced-model-updates

Domyślnie każda zmiana treści spowoduje aktualizację modelu i walidację formularza. Można zastąpić to zachowanie, używając dyrektywy ngModelOptions, aby powiązać tylko z określoną listą zdarzeń. To znaczy, ng-model-options = "{updateOn: 'blur'}" zostanie zaktualizowany i zweryfikowany dopiero po utracie ostrości przez element sterujący. Możesz ustawić kilka wydarzeń za pomocą listy rozdzielanej spacjami. Tj. Ng-model-options = "{updateOn: 'mousedown blur'}"

I odbić

Aktualizację / walidację modelu można opóźnić za pomocą klucza debounce z dyrektywą ngModelOptions. To opóźnienie będzie miało również zastosowanie do parserów, walidatorów i flag modelu, takich jak $ dirty lub $ pristine.

Tj. Ng-model-options = "{debounce: 500}" będzie czekał pół sekundy od ostatniej zmiany treści przed uruchomieniem aktualizacji modelu i walidacji formularza.

pocesar
źródło
2
W większości przypadków nie rozwiązuje to problemu, ponieważ prawdopodobnie nadal chcesz otrzymywać ng-changezdarzenia, zanim użytkownik opuści pole. Na przykład w moim przypadku żądam nowych danych z mojego API, gdy tylko pole wejściowe jest prawidłowe, ale nie chcę wyświetlać komunikatu o błędzie, gdy jest on nieprawidłowy - chcę to zrobić tylko wtedy, gdy jest nieważne i wystąpiło rozmycie.
Tom
@Tom testuję 1.3.0 rc-3i nie odpalachange dopóki blur, sprawdź to: plnkr.co/edit/RivVU3r7xfHO1hUybqMu?p=preview
Gill Bates
To naprawdę powinna być akceptowana, najlepsza odpowiedź. Po co dodawać dyrektywę, skoro można dodać jedną właściwość?
Dan Hulton
92

Od wersji 1.3.0 można to łatwo zrobić $touched, co jest prawdą po opuszczeniu pola przez użytkownika.

<p ng-show="form.field.$touched && form.field.$invalid">Error</p>

https://docs.angularjs.org/api/ng/type/ngModel.NgModelController

user1506145
źródło
1
Czy to nadal nie wykonywałoby weryfikacji po każdym kluczu? OP stwierdził, że chce przeprowadzić walidację dopiero po opuszczeniu pola przez użytkownika.
comecme
@comecme tak, domyślna opcja ng-model-to updateOn: 'default'oznacza, że ​​dla danych wejściowych, klawisza w górę i dla zaznaczeń, przy zmianie
pocesar
5
Jednak wielką wadą jest to, że nie będziesz mieć takiego samego doświadczenia z walidacją, jeśli wrócisz do pracy w terenie. Wracasz do punktu pierwszego, gdzie sprawdza się przy każdym naciśnięciu klawisza, ponieważ jest już ustawiony na dotknięcie. Jestem zaskoczony, że otrzymało tak wiele głosów za.
dudewad
3
@dudewad to jest poprawne, ale myślę, że jest to pożądane zachowanie z punktu widzenia UX - jeśli wrócisz do nieprawidłowego pola, komunikat o błędzie powinien trwać do momentu, gdy wartość będzie prawidłowa, a nie do momentu wpisania pierwszej litery.
danbars
@dudewad Czy nie pomogłoby to użytkownikowi w sprawdzaniu poprawności przy każdym naciśnięciu, gdy już wiedział, że pole jest nieprawidłowe po pierwszym wypełnieniu go? Gdy wiedzą, że pole jest nieprawidłowe, prawdopodobnie chcieliby szybko dowiedzieć się, kiedy pole jest prawidłowe, a walidacja każdego naciśnięcia klawisza przyspieszyłaby ten proces?
Alan Dunning,
32

Rozwiązałem to, rozszerzając to, co zasugerował @jlmcdonald. Stworzyłem dyrektywę, która zostanie automatycznie zastosowana do wszystkich danych wejściowych i wybierz elementy:

var blurFocusDirective = function () {
    return {
        restrict: 'E',
        require: '?ngModel',
        link: function (scope, elm, attr, ctrl) {
            if (!ctrl) {
                return;
            }

            elm.on('focus', function () {
                elm.addClass('has-focus');

                scope.$apply(function () {
                    ctrl.hasFocus = true;
                });
            });

            elm.on('blur', function () {
                elm.removeClass('has-focus');
                elm.addClass('has-visited');

                scope.$apply(function () {
                    ctrl.hasFocus = false;
                    ctrl.hasVisited = true;
                });
            });

            elm.closest('form').on('submit', function () {
                elm.addClass('has-visited');

                scope.$apply(function () {
                    ctrl.hasFocus = false;
                    ctrl.hasVisited = true;
                });
            });

        }
    };
};

app.directive('input', blurFocusDirective);
app.directive('select', blurFocusDirective);

Spowoduje to dodanie has-focusi has-visitedklasy do różnych elementów, gdy użytkownik skupia się / odwiedza elementy. Następnie możesz dodać te klasy do reguł CSS, aby wyświetlić błędy walidacji:

input.has-visited.ng-invalid:not(.has-focus) {
    background-color: #ffeeee;   
}

Działa to dobrze, ponieważ elementy nadal mają nieprawidłowe właściwości itp., Ale CSS można wykorzystać, aby zapewnić użytkownikowi lepsze wrażenia.

lambinator
źródło
2
Dlaczego „? NgModel” jest wymagane zamiast po prostu „ngModel”? Rozumiem, że istnieje wymóg ngModel, ale dlaczego znak zapytania?
Noremac
2
Dzięki za pomysł. Powinienem zwrócić uwagę, że wymaga jQuery (myślę, że nie można zobaczyć .closest () w jql).
iandotkelly
1
Nie wszystkie elementy <input> są obsługiwane przez ngModel (np. Input type = submit). The? wymaga ngModel tylko na elementach, które go obsługują.
chmanie
5
Szczerze mówiąc, nie należy odchodzić od kątowego sposobu ustawiania stanów pól wejściowych, takich jak formName.inputName.$dirty. Sugerowałbym edycję dyrektywy, aby napisać podobne wartości logiczne formName.inputName.$focused, zamiast używać nazw klas do rozmycia i wartości logicznych do walidacji.
Tom
17

Udało mi się to zrobić za pomocą całkiem prostego fragmentu CSS. Wymaga to, aby komunikaty o błędach były rodzeństwem danych wejściowych, do których się odnoszą, i aby miały klasę error.

:focus ~ .error {
    display:none;
}

Po spełnieniu tych dwóch wymagań spowoduje to ukrycie wszelkich komunikatów o błędach związanych z aktywnym polem wejściowym, co moim zdaniem i tak powinno robić angularjs. Wygląda na przeoczenie.

mikołaja
źródło
2
Ahh. To mądra droga. O wiele wolę to od pisania javascript, aby to zrobić.
pokład
5
Wadą tego podejścia jest to, że gdy ktoś poprawia błąd, błąd nie jest już widoczny, co nie jest idealne. Idealnie byłoby, gdyby błąd nie był widoczny do momentu rozmycia, ale nie znikał, dopóki nie zostanie poprawiony.
Chris Nicola,
4

Wydaje się, że jest to standardowo realizowane w nowszych wersjach angular.

Klasy ng-untouched i ng-touch są ustawiane odpowiednio przed i po tym, jak użytkownik skupił się na walidowanym elemencie.

CSS

input.ng-touched.ng-invalid {
   border-color: red;
}
Arg0n
źródło
4

Odnośnie rozwiązania @ lambinator ... Otrzymałem następujący błąd w angular.js 1.2.4:

Błąd: [$ rootScope: inprog] $ przegląd już trwa

Nie jestem pewien, czy zrobiłem coś źle, czy jest to zmiana w Angular, ale usunięcie scope.$applyinstrukcji rozwiązało problem, a klasy / stany są nadal aktualizowane.

Jeśli również widzisz ten błąd, spróbuj:

var blurFocusDirective = function () {
  return {
    restrict: 'E',
    require: '?ngModel',
    link: function (scope, elm, attr, ctrl) {
      if (!ctrl) {
        return;
      }
      elm.on('focus', function () {
        elm.addClass('has-focus');
        ctrl.$hasFocus = true;
      });

      elm.on('blur', function () {
        elm.removeClass('has-focus');
        elm.addClass('has-visited');
        ctrl.$hasFocus = false;
        ctrl.$hasVisited = true;
      });

      elm.closest('form').on('submit', function () {
        elm.addClass('has-visited');

        scope.$apply(function () {
          ctrl.hasFocus = false;
          ctrl.hasVisited = true;
        });
      });
    }
  };
};
app.directive('input', blurFocusDirective);
app.directive('select', blurFocusDirective);
Melcher
źródło
2

Możesz napisać własną dyrektywę, która otacza metodę javascript blur () (i uruchamia funkcję walidacji po uruchomieniu); istnieje problem z Angularem, który ma przykładowy (a także ogólną dyrektywę, która może wiązać się z innymi zdarzeniami, które nie są natywnie obsługiwane przez Angular):

https://github.com/angular/angular.js/issues/1277

Jeśli nie chcesz jechać tą trasą, inną opcją byłoby ustawienie $ watch na polu, ponownie uruchamiając walidację, gdy pole zostanie wypełnione.

jlmcdonald
źródło
2

Aby dalej podchwycić podane odpowiedzi, możesz uprościć tagowanie danych wejściowych, używając pseudoklas CSS3 i oznaczając odwiedzane pola tylko klasą, aby opóźnić wyświetlanie błędów walidacji, aż użytkownik straci koncentrację na polu:

(Przykład wymaga jQuery)

JavaScript

module = angular.module('app.directives', []);
module.directive('lateValidateForm', function () {
    return {
        restrict: 'AC',
        link: function (scope, element, attrs) {
            $inputs = element.find('input, select, textarea');

            $inputs.on('blur', function () {
                $(this).addClass('has-visited');
            });

            element.on('submit', function () {
                $inputs.addClass('has-visited');
            });
        }
    };
});

CSS

input.has-visited:not(:focus):required:invalid,
textarea.has-visited:not(:focus):required:invalid,
select.has-visited:not(:focus):required:invalid {
  color: #b94a48;
  border-color: #ee5f5b;
}

HTML

<form late-validate-form name="userForm">
  <input type="email" name="email" required />
</form>
Patrick Glandien
źródło
2

na podstawie odpowiedzi @nicolas .. Czysty CSS powinien być sztuczką, pokaże tylko komunikat błędu przy rozmyciu

<input type="email" id="input-email" required
               placeholder="Email address" class="form-control" name="email"
               ng-model="userData.email">
        <p ng-show="form.email.$error.email" class="bg-danger">This is not a valid email.</p>

CSS

.ng-invalid:focus ~ .bg-danger {
     display:none;
}
keithics
źródło
To dobrze, ale komunikat o błędzie zniknie, gdy użytkownik kliknie ponownie w polu, co nie jest idealne.
Bennett McElwee
2

Oto przykład użycia ng-messages (dostępne w angular 1.3) i niestandardowej dyrektywy.

Komunikat walidacyjny jest wyświetlany przy rozmyciu po raz pierwszy, gdy użytkownik opuszcza pole wejściowe, ale kiedy koryguje wartość, komunikat walidacji jest natychmiast usuwany (już nie przy rozmyciu).

JavaScript

myApp.directive("validateOnBlur", [function() {
    var ddo = {
        restrict: "A",
        require: "ngModel",
        scope: {},
        link: function(scope, element, attrs, modelCtrl) {
            element.on('blur', function () {
                modelCtrl.$showValidationMessage = modelCtrl.$dirty;
                scope.$apply();
            });
        }
    };
    return ddo;
}]);

HTML

<form name="person">
    <input type="text" ng-model="item.firstName" name="firstName" 
        ng-minlength="3" ng-maxlength="20" validate-on-blur required />
    <div ng-show="person.firstName.$showValidationMessage" ng-messages="person.firstName.$error">
        <span ng-message="required">name is required</span>
        <span ng-message="minlength">name is too short</span>
        <span ng-message="maxlength">name is too long</span>
    </div>
</form>

PS. Nie zapomnij pobrać i dołączyć ngMessages do swojego modułu:

var myApp = angular.module('myApp', ['ngMessages']);
jurgemar
źródło
1

Udokumentowano, że ng-model-options w AngularJS 1.3 (beta w chwili pisania tego tekstu) obsługuje {updateOn: 'blur'}. W przypadku wcześniejszych wersji działało dla mnie coś takiego:

myApp.directive('myForm', function() {
  return {
    require: 'form',
    link: function(scope, element, attrs, formController) {
      scope.validate = function(name) {
        formController[name].isInvalid
            = formController[name].$invalid;
      };
    }
  };
});

Z takim szablonem:

<form name="myForm" novalidate="novalidate" data-my-form="">
<input type="email" name="eMail" required="required" ng-blur="validate('eMail')" />
<span ng-show="myForm.eMail.isInvalid">Please enter a valid e-mail address.</span>
<button type="submit">Submit Form</button>
</form>
Terence Bandoian
źródło
1

Użyj stanu pola $ touched Pole zostało w tym celu dotknięte, jak pokazano na poniższym przykładzie.

<div ng-show="formName.firstName.$touched && formName.firstName.$error.required">
    You must enter a value
</div>
Kushal Sharma
źródło
0

Możesz dynamicznie ustawić klasę css has-error (zakładając, że używasz bootstrap), używając ng-class i właściwości w zakresie powiązanego kontrolera:

plunkr: http://plnkr.co/edit/HYDlaTNThZE02VqXrUCH?p=info

HTML:

<div ng-class="{'has-error': badEmailAddress}">
    <input type="email" class="form-control" id="email" name="email"
        ng-model="email" 
        ng-blur="emailBlurred(email.$valid)">
</div>

Kontroler:

$scope.badEmailAddress = false;

$scope.emailBlurred = function (isValid) {
    $scope.badEmailAddress = !isValid;
};
Mikrofon
źródło
0

Jeśli używasz bootstrap 3 i lesscss, możesz włączyć weryfikację rozmycia za pomocą następującego mniejszego fragmentu:

:focus ~ .form-control-feedback.glyphicon-ok {
  display:none;
}

:focus ~ .form-control-feedback.glyphicon-remove {
  display:none;
}

.has-feedback > :focus {
  & {
    .form-control-focus();
  }
}
Stefan
źródło
0

out Użyłem dyrektywy. Oto kod:

app.directive('onBlurVal', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs, controller) {

            element.on('focus', function () {
                element.next().removeClass('has-visited');
                element.next().addClass('has-focus');
            });

            element.on('blur', function () {

                element.next().removeClass('has-focus');
                element.next().addClass('has-visited');
            });
        }
    }
})

Cała moja kontrolka wejściowa ma element span jako następny element, w którym jest wyświetlany mój komunikat walidacji, a zatem dyrektywa jako atrybut jest dodawana do każdej kontrolki wejściowej.

Mam również (opcjonalnie) .has-focus i odwiedził klasę css w moim pliku css, do którego odwołuje się dyrektywa.

UWAGA: pamiętaj, aby dodać „on-blur-val” dokładnie w ten sposób do kontrolki wejścia bez apostrofów

user3676608
źródło
0

Używając ng-focus , możesz osiągnąć swój cel. musisz podać ng-focus w swoim polu wprowadzania. A pisząc swoje pochodne ng-show, musisz napisać logikę, która nie jest równa. Jak poniższy kod:

<input type="text" class="form-control" name="inputPhone" ng-model="demo.phoneNumber" required ng-focus> <div ng-show="demoForm.inputPhone.$dirty && demoForm.inputPhone.$invalid && !demoForm.inputPhone.$focused"></div>

Promień
źródło
0

Możemy korzystać z funkcji onfocus i onblur. Byłoby proste i najlepsze.

<body ng-app="formExample">
  <div ng-controller="ExampleController">
  <form novalidate class="css-form">
    Name: <input type="text" ng-model="user.name" ng-focus="onFocusName='focusOn'" ng-blur="onFocusName=''" ng-class="onFocusName" required /><br />
    E-mail: <input type="email" ng-model="user.email" ng-focus="onFocusEmail='focusOn'" ng-blur="onFocusEmail=''" ng-class="onFocusEmail" required /><br />
  </form>
</div>

<style type="text/css">
 .css-form input.ng-invalid.ng-touched {
    border: 1px solid #FF0000;
    background:#FF0000;
   }
 .css-form input.focusOn.ng-invalid {
    border: 1px solid #000000;
    background:#FFFFFF;
 }
</style>

Spróbuj tutaj:

http://plnkr.co/edit/NKCmyru3knQiShFZ96tp?p=preview

Naveen Kumar
źródło