Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Email form validation one at a time

I'm trying to implement form validation for an email field. Validation should do following:

  • Check if email has been entered over required attribute and display a message if no email has been entered
  • Check if the email has a valid format (seems to be done automatically without specifying an attribute) and display a message if the format is wrong
  • Check if the email is unique over a $http.get call and display a message in case the email was found and therefore can't be used

I would like that the first message appears, if the field is empty, the second message appears if the email is invalid and the third message appears, if the email address is found and therefore not unique one at a time.

This works if I try with only the "required" attribute but as soon I add my email-available directive attribute it doesn't check the format of the email any longer and the email-available directive is executed together with the required attribute. Both messages pop up but I would only like that the user sees one message at a time.

I'm using angularjs 1.1.3.

Can somebody tell me what I might me doing wrong?

HTML

<div id="user_mod" class="mod_form" ng-show="userModScreenIsVisible">
<form name="mod_user" novalidate>
    <input type="email" name="email" ng-model="user.email" placeholder="E-Mail" required email-available/>
    <div class="user-help" ng-show="mod_user.email.$dirty && mod_user.email.$invalid">Invalid:
        <span ng-show="mod_user.email.$error.required">Please enter your email.</span>
        <span ng-show="mod_user.email.$error.email">This is not a valid email.</span>
        <span ng-show="mod_user.email.$error.emailAvailable">This email address is already taken.</span>
    </div>
</form>

Directive

directive('emailAvailable', function($http) { // available
    return {
        require: 'ngModel',
        link: function(scope, elem, attr, ctrl) {
            ctrl.$parsers.unshift(function(viewValue) {
                ctrl.$setValidity('emailAvailable', false);
                if(viewValue !== "" && typeof viewValue !== "undefined") {
                    console.log("variable is defined");

                    $http.get('/api/user/email/' + viewValue + '/available')
                        .success(function(data, status, headers, config) {
                            console.log(status);
                            ctrl.$setValidity('emailAvailable', true);
                            return viewValue;
                        })
                        .error(function(data, status, headers, config) {
                            console.log("error");
                            ctrl.$setValidity('emailAvailable', false);
                            return undefined;
                        });
                } else {
                    console.log("variable is undefined");
                    ctrl.setValidity('emailAvailable', false);
                    return undefined;
                }
            });
        }
    };
});
like image 402
Christopher Armstrong Avatar asked Mar 23 '13 21:03

Christopher Armstrong


1 Answers

I see you've already solved your own problem, but I think I can offer you some tips/advice here (I hope):

1) All you needed to do to ensure that your validator was run after the built-in angular validators was push() it onto ctrl.$parsers, rather than unshift() it.

2) To keep your validator from running due to the previously run validators showing it's invalid (i.e. If you don't want to make the Ajax call if the field is already invalidated). You just need to check ctrl.$invalid in an if statement inside of your validator.

3) You'll want to invalidate your form with a separate call to $setValidity() before starting your ajax call, and after it's been received. This way, your form is invalid until the AJAX returns and says whether it's valid or not.

4) This is probably minor, but unless you add a ctrl.$formatter as well, values initially assigned to your object in $scope will not be validated before they're written to the screen. This can be a problem if your form is dynamically added to the screen via routing, ng-repeat, or ng-include with data pre-populated. Generally all validators should have a $parser component (view -> model) and a $formatter component (model -> view).

5) A word of caution. Most all validators will completely remove the value from the model if it's invalid. Becuase you're making an asynchronus call, you're going to have to return the viewValue immediately in your parser function. Usually the parser function will return undefined if the field is invalidated, which prevents invalid data from being in your model.

6) Since the validators have a state that is maintained in your $error object, you'll want to clear it out when this async validator is initially hit. See below.

7) Side Note: In your answer, I noticed you were returning values in your ajax response handlers... that's not going to do anything for you. Because the call is asynchronous, you're effectively always returning undefined from that parser. Does it update your model? I'd be surprised if it did.

Here is how I'd have altered your original directive to make it work as you probably would like:

app.directive('emailAvailable', function($http, $timeout) { // available
    return {
        require: 'ngModel',
        link: function(scope, elem, attr, ctrl) {
          console.log(ctrl);
            // push the validator on so it runs last.
            ctrl.$parsers.push(function(viewValue) {
                // set it to true here, otherwise it will not 
                // clear out when previous validators fail.
                ctrl.$setValidity('emailAvailable', true);
                if(ctrl.$valid) {
                  // set it to false here, because if we need to check 
                  // the validity of the email, it's invalid until the 
                  // AJAX responds.
                  ctrl.$setValidity('checkingEmail', false);

                  // now do your thing, chicken wing.
                  if(viewValue !== "" && typeof viewValue !== "undefined") {
                      $http.get('/api/user/email/' + viewValue + '/available')
                          .success(function(data, status, headers, config) {
                              ctrl.$setValidity('emailAvailable', true);
                              ctrl.$setValidity('checkingEmail', true);
                          })
                          .error(function(data, status, headers, config) {
                              ctrl.$setValidity('emailAvailable', false);
                              ctrl.$setValidity('checkingEmail', true);
                          });
                  } else {
                      ctrl.$setValidity('emailAvailable', false);
                      ctrl.$setValidity('checkingEmail', true);
                  }
                }
                return viewValue;
            });

        }
    };
});

And... of course, here is a plunker demonstrating it all

like image 142
Ben Lesh Avatar answered Oct 04 '22 18:10

Ben Lesh