Joel's Thoughts

Building An Age Limit Validator Directive - AngularJs

July 24, 2016

I’m going to write an AngularJs Directive version of the Age Limit Validator Plugin I wrote for one of my jQuery projects. Default settings such as age limit value, invalid age message, etc. can be easily configured. Also this directive leverages HTML5 form validation thus it plays nice with existing HTML5 forms.

Checkout the demo or download source code on Github.

I start by writing the directive’s bare structure.

    angular.module('app.widget', [])
        .directive('ageLimit', function () {           
            return {
                link: function ($scope, $element, $attrs,  ngModelCtrl) {
                }
            };
        });

The directive’s name is ageLimit and is inside the app.widget module.

Next is to add some default settings.

    angular.module('app.widget', [])
        .directive('ageLimit', function () {           
            return {
                link: function ($scope, $element, $attrs,  ngModelCtrl) {
                    var settings = {
                        minAge: 18,
                        underAgeMsg: 'You need to be at least 18 years old.',
                        title: null,
                        pattern: null
                    },
                    .... more code 
                }
            };
        });

As you can see, I have 4 default settings.

  1. minAge - This is the minimum age to accept and is an integer value. If the calculated age is less than the minAge value, we’ll notify the user and prevent the form from being submitted.
  2. underAgeMsg - This is a string. The message to show when the calculated age is invalid.
  3. title - A string and is the title attribute value of the input.
  4. pattern - A string and is the pattern attribute value for HTML5 input.

Keep in mind that all the default settings are actual attributes on the input element. Also the default values will be overridden by the set values on the elements attributes. So for example, if I do this on my view,

 <input type="text" age-limit min-age=20 title="Hi, you are under age!" />

The new minAge value is now 20 and the overridden title value is now Hi, you are under age!. Thus, I need a function that will override these default setting values with the set values from the element’s attributes. I’ll call it copyUserSettings.

    angular.module('app.widget', [])
        .directive('ageLimit', function () {           
            return {
                link: function ($scope, $element, $attrs,  ngModelCtrl) {
                    var settings = {
                        minAge: 18,
                        underAgeMsg: 'You need to be at least 18 years old.',
                        title: null,
                        pattern: null
                    },
                    //override default values
                    copyUserSettings = function (attrs, settings) {
                            var property;
                            for (property in settings) {
                                if (settings.hasOwnProperty(property) && attrs.hasOwnProperty(property)) {
                                    settings[property] = attrs[property];
                                }
                            }
                        },
                    .... more code 
                }
            };
        });

Next is I need to write a function that will calculate age based from the entered date.

    angular.module('app.widget', [])
        .directive('ageLimit', function () {           
            return {
                link: function ($scope, $element, $attrs,  ngModelCtrl) {
                    ....more code

                    calculateAge = function (birthday, settings) { 
                        var regex, today, birthDate, age, month;
                        if (!settings.pattern) {
                            //validate in mm-dd-yyyy format
                            regex = /^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/; 
                            if (!regex.test(birthday)) {
                                return -1;
                            }
                        }
                        today = new Date();
                        birthDate = new Date(birthday);
                        age = today.getFullYear() - birthDate.getFullYear();
                        month = today.getMonth() - birthDate.getMonth();
                        if (month < 0 || (month === 0 
                                  && today.getDate() < birthDate.getDate())) {
                            age = age - 1;
                        }

                        if (isNaN(age)) {
                            return -1;  //if age is invalid
                        }

                        return age;
                    };

                    .... more code 
                }
            };
        });

The calculateAge function primarily checks for the value of the pattern attribute from the element. If there’s no set pattern value, it will validate the birthday value (entered date) in mm-dd-yyyy format. If the entered date is in the right format, function will then proceed to calculate the age based from the birthday value.

Now for the actual validation,

 angular.module('app.widget', [])
        .directive('ageLimit', function () {
            /*jslint unparam: true*/
            return {
                link: function ($scope, $element, $attrs,  ngModelCtrl) {
                    ....more code

                    //where actual validation happens
                    copyUserSettings($attrs, settings);
                    $element.on('focusout keydown', function (e) {
                        var age = calculateAge(e.target.value, settings);
                        e.target.setCustomValidity(""); //clear validation               
                        if (-1 === age && settings.title) {
                            e.target.setCustomValidity(settings.title);
                            return;
                        }
                        if (age < parseInt(settings.minAge, 10)) {
                            e.target.setCustomValidity(settings.underAgeMsg);
                        }
                    });
                }
            };
        });

Actual validation starts by calling the copyUserSettings function. AngularJs’ $attrs object exposes all the set attribute values from the element ($element). Next is I hooked up an event listener function to the element which fires if either focusout or keydown event occurs.

See the directive’s whole code here or see the readme on how to use on your existing AngularJs application.









  • About
  • Search
  • Resume
  • Powered by Jekyll using the Trio theme