Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle three part date field validation with angularjs

I have a three part date field I need to validate in angular js. I have got as far as creating a custom validation function, but I am having trouble designing the logic of how the fields should update each other's status.

How can I get all three form fields to sing off the same hymn sheet, and all show their status as either valid or invalid depending on the others?

Here is the fiddle: http://jsfiddle.net/4GsMm/1/

And code:

<div ng-app="myApp" ng-controller="myCtrl">
    <form action="" name="myForm">
        <div class="date-group">
            <input type="text" name="day" ng-model="day" ng-valid-func="validator" />
            <input type="text" name="month" ng-model="month" ng-valid-func="validator" />
            <input type="text" name="year" ng-model="year" ng-valid-func="validator" />
        </div>
    </form>
</div>

and...

input.ng-invalid{
    background-color: #fdd !important;    
}

input.ng-valid{
    background-color: #dfd !important;    
}

input{
    display: inline;
    width: 3em;
}

and...

var app = angular.module('myApp', [])

var myCtrl = function($scope){

    $scope.day = "01"
    $scope.month = "01"
    $scope.year = "2000"

    $scope.validator = function(val){
        var day = $('[name=day]').val()
        var month = $('[name=month]').val()
        var year = $('[name=year]').val()
        var d = new Date([year,month,day].join('-'))
        console.log(d, [year,month,day].join('-'))
        return d > new Date('2000-01-01')
    }

}

app.directive('ngValidFunc', function() {
  return {
    require: 'ngModel',
    link: function(scope, elm, attrs, ctrl) {
      ctrl.$parsers.unshift(function(viewValue) {
        if (attrs.ngValidFunc && scope[attrs.ngValidFunc] && scope[attrs.ngValidFunc](viewValue, scope, elm, attrs, ctrl)) {
          ctrl.$setValidity('custom', true);
        } else {
          ctrl.$setValidity('custom', false);
        }
        return elm.val()
      });
    }
  };
});
like image 764
Billy Moon Avatar asked Feb 16 '23 04:02

Billy Moon


2 Answers

I too was looking for three part date field validation with AngularJS, here is what I did (working for me)

HTML:

<form name="myForm" method="post" novalidate ng-submit="somefuncion()" ng-controller="Controller" ng-app="testModule">
    Date (MM-DD-YYYY): <input id="partnerDOBmm" name="partnerDOBmm" class="ddinput" type="text" value="" size="2" maxlength="2" ng-model="partnerDOBmm" required only-digits ng-minlength="2" ng-change="validateDOB(partnerDOBmm,partnerDOBdd,partnerDOByyyy)"  />
    -
    <input id="partnerDOBdd" name="partnerDOBdd" class="ddinput" type="text" value="" size="2" maxlength="2" ng-model="partnerDOBdd" required only-digits ng-minlength="2" ng-change="validateDOB(partnerDOBmm,partnerDOBdd,partnerDOByyyy)" />
    -
    <input id="partnerDOByyyy" name="partnerDOByyyy" class="yyyyinput" type="text" value="" size="4" maxlength="4" ng-model="partnerDOByyyy" required only-digits ng-minlength="4" ng-change="validateDOB(partnerDOBmm,partnerDOBdd,partnerDOByyyy)" />
    <br />
    <span class="error" ng-show="submitted && (myForm.partnerDOBmm.$error.required || myForm.partnerDOBdd.$error.required || myForm.partnerDOByyyy.$error.required)"> Required! </span>
    <span class="error" ng-show="submitted && (myForm.partnerDOBmm.$error.minlength || myForm.partnerDOBdd.$error.minlength || myForm.partnerDOByyyy.$error.minlength)"> Too Short! </span>
    <span class="error" ng-show="notValidDate && !(myForm.partnerDOBmm.$error.required || myForm.partnerDOBdd.$error.required || myForm.partnerDOByyyy.$error.required || myForm.partnerDOBmm.$error.minlength || myForm.partnerDOBdd.$error.minlength || myForm.partnerDOByyyy.$error.minlength)"> Not a Valid Date! </span>
    <br />
    <button type="submit" class="btnSubmit" ng-click="submitted=true">Submit</button>
</form>

Script:

function Controller ($scope) {
    $scope.notValidDate = false;
    $scope.somefuncion = function(event) {
        if ($scope.myForm.$valid) {
            alert("Form is working as expected");
            return true;
        }
        else
        {
            alert("Something is not correct");
            return false;
        }
    };
    $scope.validateDOB  = function(mm,dd,yyyy)
    {
        var flag = true;
        var month = mm;
        var date = dd;
        var year = yyyy;
        if(month == null || date == null || year == null || month.length != 2 || date.length!= 2 || year.length!= 4)
            flag = false;
        if(month < 1 || month > 12)
            flag = false;
        if(year < 1900 || year > 2100)
            flag = false;
        if(date < 1 || date > 31)
            flag = false;

        if((month == 04 || month == 06 || month == 9 || month == 11) && (date >= 31))
            flag = false;

        if(month == 02)
        {
            if(year % 4 != 0)
            {
                if(date > 28)
                    flag = false;
            }
            if(year % 4 == 0)
            {
                if(date > 29)
                    flag = false;
            }
        }

        var dob = new Date();
        dob.setFullYear(year, month - 1, date);
        var today = new Date();
        if(dob > today)
            flag = false;

        if(flag)
        {
            $scope.notValidDate = false;
            $scope.myForm.$valid = true;
        }
        else
        {
            $scope.notValidDate = true;
            $scope.myForm.$valid = false;
            form.partnerDOBmm.focus();
        }
    }
}

angular.module('testModule', [])
.directive('onlyDigits', function () {

    return {
        restrict: 'A',
        require: '?ngModel',
        link: function (scope, element, attrs, ngModel) {
            if (!ngModel) return;
            ngModel.$parsers.unshift(function (inputValue) {
                var digits = inputValue.replace(/[^\d]/g, "");
                ngModel.$viewValue = digits;
                ngModel.$render();
                return digits;
            });
        }
    };
});

Feel free to improvise the answer, hopefully you can use 'else' in 'validateDOB' function

like image 181
Hamed Ali Khan Avatar answered Feb 17 '23 16:02

Hamed Ali Khan


Realistically, you're better off just using input type="number" and max and min validators, and adding an ng-change directive that calls a function to update the date.

Since the change isn't fired if the content of the input is invalid, you'll never get a "bad" date:

<input type="number" name="year" ng-model="year" min="2000" ng-change="updateDate()"/>
<input type="number" name="month" ng-model="month" min="1" max="12" ng-change="updateDate()" />
<input type="number" name="day" ng-model="day" min="1" max="31" ng-change="updateDate()" />

Here's a plunk illustrating this solution.

However using text boxes for the month and the day are potentially bad solutions, as it will become a lot more complicated make sure than the day value is kosher. (Think about February and leap year for example). For this whole solution I would recommend using a drop down for the days at the very least, and probably for the month too... as there is a fixed set of results necessary, and you can show or hide the day options based on the value of the month option.

Here's an example of that:

<form name="myForm">
    <input type="number" name="year" ng-model="year" min="2000" ng-change="updateDate()"/>
    <select name="month" ng-model="month" ng-change="updateDate()">
      <option value="1">Jan</option>
      <option value="2">Feb</option>
      <option value="3">Mar</option>
      <option value="4">Apr</option>
      <option value="5">May</option>
      <option value="6">Jun</option>
      <option value="7">Jul</option>
      <option value="8">Aug</option>
      <option value="9">Sep</option>
      <option value="10">Oct</option>
      <option value="11">Nov</option>
      <option value="12">Dec</option>
    </select>
    <select name="day" ng-model="day" ng-change="udpateDate()">
      <option>1</option>
      <option>2</option>
      <option>3</option>
      <!-- ... SNIP!... -->
      <option>27</option>
      <option>28</option>
      <option ng-show="month != 2 || !(year % 4)")>
        29
      </option>
      <option ng-show="month != 2">
        30
      </option>
      <option ng-show="month == 1 || month == 3 || month == 5 || month == 7 || 
          month == 8 || month == 10 || month == 12">
        31
      </option>
    </select>
    <p>
      {{date | date: 'yyyy-MMM-dd'}}
    </p>
  </form>

But, but why not dynamically create those selects?

Could you dynamically create the above selects? Sure. Is it worth it? Maybe? Probably not. It only took 30 seconds to type up the options along with the show/hyde logic.

And here's that example in a plunk.

In both of the above scenarios you can just validate on the year validation for 2001:

<span ng-show="myForm.year.$error.min">Must be after January 1, 2001</span>
like image 43
Ben Lesh Avatar answered Feb 17 '23 17:02

Ben Lesh