Wednesday 29 April 2015

AngularJS Custom Validator Example - Comparing Two Dates

Below is an example of AngularJS custom validator to compare an end date with a start date to make sure that the end date is equal or bigger than the start date. To see basic usage of AngularJS validation, please see my previous post.

HTML codes:
<!DOCTYPE html>
<html>

  <head>
    <script src="https://code.angularjs.org/1.4.0-beta.6/angular.js"></script>
    <script src="https://code.angularjs.org/1.3.15/angular-messages.js"></script>
    <script src="script.js"></script>
  </head>

  <body ng-app="validationExample">
    <div ng-controller="MyCtrl as vm">
      <form name="myForm" novalidate="" ng-submit="vm.submitted(myForm, vm.input)">
        <span class="input-label">Start Date</span> 
        <input type="date" name="startDate" ng-model="vm.input.startDate" required />
        <span ng-messages="myForm.startDate.$error" ng-messages-include="errors-template.html"></span>
        <br />       
        <span class="input-label">End Date</span> 
        
        <input type="date" name="endDate" ng-model="vm.input.endDate" start-date="{{vm.input.startDate.yyyymmdd()}}" compare-with-start-date />
        <span ng-messages="myForm.endDate.$error" ng-messages-include="errors-template.html"></span>
        <br />             
        <button>submit</button>
      </form>
    </div>
    
    <!-- error messages template -->
    <script id="errors-template.html" type="text/ng-template">
        <span ng-message="required">* required!</span>
        <span class="form-error" ng-message="checkEndDate">* end date cannot be earlier than start date</span>
    </script>
  </body>

</html>

JavaScript codes:
var myApp = angular.module('validationExample', ['ngMessages'])

myApp.controller('MyCtrl', [function () {
    var vm = this;
    vm.submitted = function(form, input) {
      if(form.$valid) {
        alert('submitted');
      }
    }
} ]);

myApp.directive("compareWithStartDate", function () {
    return {
        restrict: "A",
        require: "?ngModel",
        link: function (scope, element, attributes, ngModel) {
            validateEndDate = function (endDate, startDate) {
                if (endDate && startDate) {
                    startDate = new Date(startDate);
                    startDate.setHours(0, 0, 0, 0);
                    endDate = new Date(endDate);
                    endDate.setHours(0, 0, 0, 0);
                    return endDate >= startDate;
                }
                else {
                    return true;
                }
            }

            // use $validators.validation_name to do the validation
            ngModel.$validators.checkEndDate = function (modelValue) {
                var startdate = Date.parse(attributes.startDate);
                var enddate = modelValue;
                return validateEndDate(enddate, startdate);
            };
            
            // use $observe if we need to keep an eye for changes on a passed value
            attributes.$observe('startDate', function (value) {
                var startdate = Date.parse(value);
                var enddate = Date.parse(ngModel.$viewValue);
                
                // use $setValidity method to determine the validation result 
                // the first parameter is the validation name, this name is the same in ng-message template as well
                // the second parameter sets the validity (true or false), we can pass a function returning a boolean
                ngModel.$setValidity("checkEndDate", validateEndDate(enddate, startdate));
            });
        }
    };
});


// function to parse date time object into yyyy-mm-dd format string
Date.prototype.yyyymmdd = function () {
    var yyyy = this.getFullYear().toString();
    var mm = (this.getMonth() + 1).toString(); // getMonth() is zero-based         
    var dd = this.getDate().toString();

    return yyyy + '-' + (mm[1] ? mm : "0" + mm[0]) + '-' + (dd[1] ? dd : "0" + dd[0]);
};

See the example in Plunker.

Thursday 16 April 2015

Form Validation in Ionic or AngularJS

On this post we will see how to do form validation in AngularJS or Ionic framework. At the moment, Ionic framework does not have its own validation features and relies on the underlying framework.

Basic Usage with Built-In Validators
To begin, first we need to make sure that we have a reference to angular.js file. Then we need to do the following steps:
- give a name to our form
- put novalidate="" attribute on the form
So we should have something like this on our form:
<form name="myForm" novalidate="" ng-submit="" >
 . . .
</form>
- add a unique name to each input field
- add validation attributes that we need on the input fields. AngularJS provides some validation directives such as these; required, pattern, minlength, maxlength, min and max. It also has basic built-in validators for these input types; text, number, url, email and date.
An example on an input field:
<input type="text" name="name" ng-model="vm.input.name" required />
- add an error message to be displayed for each validator checking the corresponding property name on input field's $error object in conditional statement like ng-if or ng-show. Properties under $error object with the same names as the validators' names exist for us to use. The properties are on this format; formName.fieldName.$error.builtInValidatorName. For example:
<span ng-show="myForm.name.$error.required == true">* required</span>
- on a method that is called in ng-submit, check whether the form is valid using formName.$valid before doing any action

Below is a complete example:
<!-- HTML page -->
<body ng-app="validationExample">
    <div ng-controller="MyCtrl as vm">
      <form name="myForm" novalidate="" ng-submit="vm.submitted(myForm, vm.input)">
        <div>
          <input type="text" name="name" ng-model="vm.input.name" required>
          <span ng-show="myForm.name.$error.required == true">test: * required</span>
        </div>
        <button>submit</button>
      </form>
    </div>
</body>

// JavaScript codes
var myApp = angular.module('validationExample', [])

myApp.controller('MyCtrl', [function () {
    var vm = this;
    vm.submitted = function(form, input) {
      if(form.$valid) {
        // post the form
        . . .
      }
    }
} ]);
See these codes on Plunker.


Creating Custom Validator
If we would like to create a custom validator, we would need to do these in addition of the above steps:
- create a directive
- on the directive's link function, use the new AngularJS $validators object on ngModel controller to do the validation. We need to create a function property on ngModel.$validators. By doing so, we register the function as a new validation rule. Each time the model changes, the validation rule is invoked. For example:
link: function(scope, element, attributes, ngModel) {
 ngModel.$validators.wrongInitial = function(modelValue) {
   . . .
 };
}
A corresponding property on input field's $error object is also created that can be used for checking validity.
- similar like in using built-in validator, we need to add an error message to be displayed by checking the newly created corresponding property on the input field's $error object:
<span ng-show="myForm.name.$error.wrongInitial == true">* must start with letter r</span>

Additional codes to be added from the first example:
<!-- HTML page -->
<input type="text" name="name" ng-model="vm.input.name" start-with-r />
<span ng-show="myForm.name.$error.wrongInitial == true">* must start with letter r</span>

// JavaScript codes
myApp.directive("startWithR", function() {
    return {
      restrict: "A",
      require: "?ngModel",
      link: function(scope, element, attributes, ngModel) {
        ngModel.$validators.wrongInitial = function(modelValue) {
          if (modelValue) {
            return modelValue.charAt(0).toUpperCase() == 'R';
          }
          else {
            return true;
          }
        };
      }
    };
});
See a complete example on Plunker.


Using ngMessages to Display Error Messages
In the recent version of AngularJS, started with version 1.3, there is a new feature that we could use to display messages including form error messages. To use this, we need to:
- include a reference to angular-messages.js file in our page
- inject ngMessages module to our AngularJS app
var myApp = angular.module('validationExample', ['ngMessages'])
- if there are only a few fields to be validated, we could specify each error message for each field like this:
<!-- use ng-messages with each field's $error object in this format; formName.fieldName.$error -->
<div ng-messages="myForm.name.$error" >  
        <!-- specify an error message for each validator -->
 <span ng-message="required">Name is required!</span>
 <span ng-message="wrongInitial">Name must start with letter r</span>
</div>
- however if we know that we are going to repeat this multiple times, we could use a template instead:
<!-- create a template by using script tag with type="text/ng-template" and give an Id -->
<script id="errors-template.html" type="text/ng-template" >  
        <!-- specify an error message for each validator -->
 <span ng-message="required">* required!</span>
 <span ng-message="wrongInitial">* must start with letter r!</span>
</script>
Then use the template to display error message(s) on each field:
<!-- use ng-messages with each field's $error object and ng-messages-include referring to the template Id -->
<span ng-messages="myForm.name.$error" ng-messages-include="errors-template.html" />
See the complete example on Plunker.


References:
Working with Validators and Messages in AngularJS
Validation in Ionic Framework Apps with ngMessages