Managing date validation for a three-part field using AngularJS

I am currently working on validating a three-part date field using AngularJS. I have managed to create a custom validation function, but I am struggling with determining how the fields should update each other's status.

How can I ensure that all three form fields are in sync and display their status as either valid or invalid based on the others?

You can find the fiddle here: http://jsfiddle.net/4GsMm/1/

Below is the code snippet:

<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()
      });
    }
  };
});

Answer №1

If you are also searching for a way to validate a three-part date field using AngularJS, here is an approach that worked 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 enhance the solution further and consider using 'else' in the 'validateDOB' function.

Answer №2

Practically speaking, the most efficient approach would be to utilize the input type="number" along with max and min validators, while incorporating an ng-change directive to trigger a function for updating the date.

By ensuring that the change event is not triggered if the input content is invalid, you can guarantee that only valid dates are processed:

<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()" />

This plunk provides a visual representation of the solution.

On the other hand, employing text boxes for month and day may lead to complications, especially when ensuring the validity of the day value (considering scenarios like February and leap year). To enhance this solution, it's recommended to use dropdowns for days at the very least, and possibly for months as well, since there is a fixed range of acceptable values which can be dynamically updated based on the selected month.

Here's a sample implementation:

<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>
      <!-- ... Other month options... -->
    </select>
    <select name="day" ng-model="day" ng-change="updateDate()">
      <option>1</option>
      <option>2</option>
      <option>3</option>
      <!-- ... SNIP!... -->
    </select>
    <p>
      {{date | date: 'yyyy-MMM-dd'}}
    </p>
  </form>

But, why not generate these selects dynamically?

While it is possible to create the above selects dynamically, the time and effort spent in doing so might not outweigh the benefits. Writing out the options and logic took mere seconds, making manual creation a viable option.

Take a look at this plunk for a live example.

In both approaches mentioned above, validation can be solely based on the year for checking against 2001:

<span ng-show="myForm.year.$error.min">Must be after January 1, 2001</span>

Answer №3

I encountered a similar issue before. The key is to avoid checking the fields in relation to each other because it can make validating the date as a whole quite challenging, especially if dealing with dates like the 30th of February. The best approach is to validate the entire date through an intermediary element.

It's worth noting that validation tasks should ideally be handled at the directive level rather than involving the controller. This ensures reusability and consistency with Angular, allowing for validator chains. For instance, you may need to validate just a valid date in one scenario, and check a date of birth for age in another, avoiding code duplication.

To tackle this, I devised a simple solution aligned with Angular principles. The trick involves using an intermediary form element (e.g., a hidden input) to consolidate all three dropdowns and perform validation on the complete date at once.

Here's the HTML snippet for setting focus:

<form name="dateForm" novalidation>
  <input type="hidden"
      ng-model="modelDate"
      date-type-multi="viewDate"
      ng-init="viewDate = {}"
      class="form-control"
  />
  <select ng-model="viewDate.day">
    ...
  </select>
  <select ng-model="viewDate.month">
    ...
  </select>
  <select ng-model="viewDate.year">
    ...
  </select>
</form>

And here's the corresponding JS code:

angular.module('customDateApp', []).
  directive('dateTypeMulti', function() {
    return {
      priority: -1000,
      require: 'ngModel',
      link: function(scope, elem, attrs, ngModel) {
        // Implementation logic
      }
    };
  });

Remember to assign a low priority to the directive so that the parser works first (as they run in reverse order compared to formatters), ensuring proper parsing by subsequent validators.

You can experiment with the functionality here: http://example.com/custom-date-validation

For a detailed breakdown of the thought process, refer to: http://example.com/multiple-fields-angular-validation

Hope this sheds light on your query! Regards, [Your Name]

Answer №4

To enhance our form validation, we introduce a custom directive to the final field and leverage $watchCollection within the directive as illustrated (pardon the usage of coffeescript).

@customValidationDirective = ->
    require: "ngModel"
    link: (scope, elem, attr, ngModel) ->
        # Retrieve the main model name without the appended _day _month _year
        # For instance, for the field 'birth_date_year', this would be 'birth_date'
        mainModelName = attr.ngModel.replace('_day', '').replace('_month', '').replace('_year', '')

        # Use the main model name to monitor all models within the designated fieldset
        scope.$watchCollection '['+mainModelName+'_day, '+mainModelName+'_month, '+mainModelName+'_year]', ->
            # Establish global value
            value = [scope.$eval(mainModelName+'_year'),
                    scope.$eval(mainModelName+'_month'), 
                    scope.$eval(mainModelName+'_day')]

            # << perform your validation logic here >>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to draw a rectangle outline in Vue.js without the need for any additional packages?

I have been attempting to craft a rectangle outline using vueJS, but so far I have not had much success. I am hoping to achieve this using only CSS, but despite my efforts, I have not been able to find a solution. My goal is to create something similar to ...

Incorporate a personalized array into Google Autocomplete Places using AngularJS

I'm working on my application and I've implemented autocomplete for Google Places using the gm-places-autocomplete directive. However, I would like to enhance this autocomplete feature by incorporating my custom array of locations along with the ...

Change a reactive variable based on an event

I am currently working on a file upload feature in my application. Template.uploadFile.events({ 'change .set-file': function ( event, template ) { var file = event.currentTarget.files[0]; [...] } }); My goal is to read each row fro ...

Accessing Facebook through the React create app login

I recently developed a React Webapp using the create-react-app module. My current challenge involves integrating Facebook login, but I'm encountering some obstacles. I am unsure about where to incorporate the Facebook JavaScript SDK asynchronously to ...

The functionality of making Slim POST requests is currently not functioning as expected within the Ionic

An issue is arising with my app that makes calls to a REST API using POST and GET methods. The app I'm developing with Ionic works perfectly when emulated using the command: ionic serve --lab However, when running the app on an actual device, calls ...

What is the best way to keep track of choices made in 'mat-list-option' while using 'cdk-virtual-scroll-viewport'?

I have been working on implementing mat-list-option within cdk-virtual-scroll-viewport in an Angular 14 project. I came across a demo project in Angular 11 that helped me set up this implementation. In the Angular 11 demo, scrolling functions perfectly an ...

The resource-intensive nature of ExpressJs HTTP Streaming is causing my disk space to deplete rapidly

My express server app.get('/data', (_, res) => { const interval = setInterval( () => res.write(`${Math.random().toString()}\n`), 1000 ); res.on('close', () => { clearInterval(interval); ...

The value attribute in the HTML input tag being dynamically increased by JavaScript

Hi there, can someone help me figure out how to save changes to the value attribute of an HTML input tag that is being incremented by JavaScript? Currently, every time I click on a specific element, the input field should increase by one. The problem is th ...

KineticJS: Applying a filter to an image does not result in the image having a stroke

Working with KineticJS version 5.1.0 I encountered an issue where a KineticJS image that had a stroke lost the stroke after applying a filter to it. I have created a demo showcasing this problem, which can be viewed on JSFiddle. Here is the code snippet: ...

The undefined dispatch function issue occurs when trying to pass parameters from a child component to React

There is an action that needs to be handled. It involves saving a new task with its name and description through an API call. export const saveNewTask = (taskName, taskDescription) => async dispatch => { console.log(taskName, taskDescription); c ...

Issues with data communication in AJAX and Node JS/Express post requests

I'm currently exploring Node.js and I'm running into an issue with app.post. Everything seems to be working fine, as I can see the console log whenever the action is executed. However, the data sent by AJAX from main.js does not seem to be receiv ...

What is the reason behind using angle brackets to access the initial value of an array with a variable inside?

I've been experimenting with this technique several times, but I still can't quite grasp why it works and haven't been able to find an answer. Can someone please explain? let myArray = [0, 1] let [myVar] = myArray console.log(myVar) // outpu ...

Validating date inputs with ng-change in AngularJS

I am currently utilizing AngularJS along with AngularJS bootstrap within my webpage. One of the components I have is a date picker directive that is structured like this: <div class="form-group {{dateStatus.class}}"> <p class="input-g ...

Enhance the interoperability of Babel with Express.js by steering clear of relative

My current approach to imports is as follows: import router from '../../app/routes' Is there a way to avoid using ../../, for example: import router from 'app/routes'? In typescript, I can achieve this with the following configuratio ...

Assign the output of a function to a variable

I am trying to retrieve data from a function call in nodejs and assign it to a variable. The desired output should be "Calling From Glasgow to Euston", but I'm currently getting "Calling From undefined to undefined". Here is the code snippet: functi ...

Tips for updating form tag details within the same blade file upon reloading

I have set up a payment page that displays the total amount to be paid. To facilitate payments through a 3rd party gateway, I have utilized a form tag on the page. Now, I am looking to integrate a promo code feature on this payment page so that the total a ...

Whenever I attempt to start the server using npm run server, I encounter the following error message: "Error: Unable to locate module './config/db'"

This is the server.jsx file I'm working with now: Take a look at my server.jsx file Also, here is the bd.jsx file located in the config folder: Check out the db.jsx file Let me show you the structure of my folders as well: Explore my folder structur ...

A step-by-step guide on transferring data from an HTML file to MongoDB using Python Flask

I am currently developing a web application that involves uploading multiple CSV files and transferring them to MongoDB. To build this application, I have utilized Python Flask. To test out different concepts for the application, I have created a sample f ...

End of container Bootstrap 4 row with two columns at the edge

I'm currently working on an angular project with bootstrap 4. Within the container, there are two rows. I specifically want one row to always be positioned at the bottom of the container. Despite trying methods like justify-content: flex-end;, botto ...

Using Vue along with bootstrap-vue: Ensuring only one list element is expanded in a list (v-for) at any given time

In my Vue project with bootstrap-vue, I am utilizing the b-collapse feature within a v-for loop for lists. While it is functioning correctly, I am struggling to automatically close expanded list elements when a new one is clicked. Here is my question: Ho ...