I have 2 date fields: departure / arrival.
Both have validation on them using a directive.
Here is what the directive looks like for the "arrival" date range:
(function(angular) {
'use strict';
function dateRangeToValidatorDirective () {
return {
restrict: 'A',
require : 'ngModel',
link : function (scope, element, attrs, ngModelCtrl) {
function validateToDateRange (value) {
var valid = true;
if (scope.$eval(attrs.dateRangeValue) && value) {
var arrivalDate = Date.parse(value);
var departureDate = Date.parse(scope.$eval(attrs.dateRangeValue));
valid = arrivalDate >= departureDate;
if (valid) {
ngModelCtrl.$setValidity('toDateRange', true);
ngModelCtrl.$setValidity('fromDateRange', true); // Why doesn't this work?
}
else {
ngModelCtrl.$setValidity('toDateRange', false);
}
}
return value;
}
ngModelCtrl.$parsers.push(validateToDateRange);
}
}
}
angular
.module('components.shared')
.directive('dateRangeToValidator', dateRangeToValidatorDirective)
})(window.angular);
HTML usage:
<input type="text" name="arrivalDate" maxlength="10" required
date-picker date-range-to-validator
date-range-value="$ctrl.newFlight.departureDate"
placeholder='MM/DD/YYYY'
ng-model="$ctrl.newFlight.arrivalDate"
id="nf_arrivalDate" size="10" />
<div class="input-error"
ng-show="$ctrl.newFlight.departureDate.length &&
$ctrl.newFlight.arrivalDate.length &&
newFlight.arrivalDate.$dirty &&
newFlight.arrivalDate.$invalid">
Arrival Date cannot precede Departure Date!
</div>
The "departure" date range is the same with a few minor differences.
(no need to fill up the screen with the same code)
This works except in the following use case(s):
Step 1. Set departure date: 12/28/2017
Step 2. Set arrival date: 12/27/2017
===> Error is shown "Arrival Date cannot precede Departure Date!"
Step 3. Modify departure date: 12/26/2017
Actual: "Arrival Date" error message does not go away.
Expected: "Arrival Date" error message goes away.
Question: How do I get one directive that is on one element to affect the $valid state of another element that is using another directive?
Please notice my comment in the if (valid) block where I am asking why doesn't this work? If I set the validity of the model controller in one directive - why can't I get at the same model controller from another directive? I assume that this is case because that isn't working...
I ended up adding a couple of controller functions that would get invoked depending on the date field that changed using ng-change. First I needed to expose the form to my controller; hence you will notice that my form name changed. Once I could access the form from my controller, changing the validity of the element was easy.
function checkDepartureDate() {
if (ctrl.newFlightForm.arrivalDate.$valid &&
ctrl.newFlight.departureDate.length &&
ctrl.newFlightForm.departureDate.$invalid) {
ctrl.newFlightForm.departureDate.$setValidity("fromDateRange", true);
}
}
}
<input type="text" name="arrivalDate" maxlength="10" required
date-picker date-range-to-validator
date-range-value="$ctrl.newFlight.departureDate"
placeholder='MM/DD/YYYY'
ng-model="$ctrl.newFlight.arrivalDate"
ng-change="$ctrl.checkDepartureDate()"
id="nf_arrivalDate" size="10" />
Related
I have an angularjs application and have to do form validation with custom business rules.
The problem is that my validation rules for a particular input field is dependent on other fields, and I don't know how to trigger the validation other than when the actual modelvalue changes.
The case is a dynamic list of employees each with a dynamic list of times of day to be entered. One rule is that these times must not overlap, which means one value can be invalid due to another value being changed and vice-versa.
I also have to show an error message for each field.
The form content is generated from the datamodel with a few layers of nested repeaters.
I have made a custom directive that contains the different validation rules and it triggers nicely when that field changes.
I'm using ngMessages to show the appropriate errormessage based on what business rule is violated.
The question is, how do I trigger validation on all other fields, when one particular field is changed? Preferably I should just trigger validation of all fields for the employee, whos value is being changed, since the values for one employee doesn't affect validation of other employees.
The fiddle here has a simplified version of my case, where the "overlap" rule just checks if two numbers are the same.
The html:
<form name="demoForm">
<div ng-repeat="employee in list">
<div ng-bind="employee.name"></div>
<div ng-repeat="day in employee.days" ng-form="employeeForm">
<input ng-model="day.hours" name="hours" custom-validate="{day: day, days: employee.days}" ng-model-options="{allowInvalid:true}" />
<span ng-messages="employeeForm.hours.$error">
<span ng-message="number">Should be a number.</span>
<span ng-message="businessHours">The number is outside business hours.</span>
<span ng-message="max">The number is too large.</span>
<span ng-message="overlap">The number must be unique for each employee.</span>
</span>
</div>
<br/>
</div>
</form>
The validation directive:
angular.module('app').directive('customValidate', [validator]);
function validator() {
return {
restrict: 'A',
require: 'ngModel',
scope: {
data: '=customValidate'
},
link: linkFunc,
};
function linkFunc(scope, element, attrs, ctrl) {
ctrl.$validators.number = function(value) {
return value === "" || Number.isInteger(+value);
}
ctrl.$validators.businessHours = function(value) {
// imagine other validation data here
return value === "" || (value >= 1 && value <= 10);
}
ctrl.$validators.overlap = function(value) {
if (value === "") {
return true;
}
// find all other entries with identical value excluding self
var identical = scope.data.days.filter(function(x) {
return x !== scope.data.day && +x.hours === +value;
});
return identical.length === 0;
};
}
}
Fiddle here:
http://jsfiddle.net/maxrawhawk/dvpjdjbv/
The answer:
This little piece of code in the end of the directives link function:
scope.$watch('data', function(){
ctrl.$validate();
}, true);
Watch the data related to validation given from the markup with the most important detail 'true' as third parameter, making $watch check for object equality.
Updated fiddle:
http://jsfiddle.net/maxrawhawk/dvpjdjbv/12/
I am new in angular and I stuck at point where I am unable to restrict the user to enter valid date manually. It sets the date as undefined for empty, characters as well as invalid date entered by the user. The date format I have mentioned is MM-dd-yyyy. Even if I enter 85/34/2102 it assumes this as a date and takes some junk value instead of validating and throwing an error.
my code of angular date-picker in html
<input type="text" class="form-control" datepicker-popup="{{clCtrl.format}}"
ng-model="clCtrl.QualityExpirationDate" is-open="clCtrl.openedQualityDate"
min-date="clCtrl.minDate" datepicker-options="clCtrl.dateOptions"
ng-required="true" close-on-date-selection="true" show-button-bar="false" />
and code in angular controller side
self.dateOptions = {
formatYear: 'yy',
startingDay: 1
};
self.formats = ['MM-dd-yyyy', 'MM/dd/yyyy', 'MM.dd.yyyy', 'shortDate'];
self.format = self.formats[0];
self.openQualityDate = function ($event) {
$event.preventDefault();
$event.stopPropagation();
self.openedQualityDate = true;
};
self.toggleMin = function () {
self.minDate = self.minDate ? null : new Date();
};
self.toggleMin();
self.clear = function () {
self.QualityExpirationDate = null;
};
Add a 'readonly' attribute to the input field:
<input type="text" readonly class="form-control" datepicker-popup="{{clCtrl.format}}" ng-model="clCtrl.QualityExpirationDate" is-open="clCtrl.openedQualityDate" min-date="clCtrl.minDate" datepicker-options="clCtrl.dateOptions" ng-required="true" close-on-date-selection="true" show-button-bar="false" />
If you want to allow user input but restrict to valid dates you could add this to your controller:
$scope.$watch('dt', function(val) {
$scope.isValidDate = isNaN(new Date(val).getTime());
});
then use the 'isValidDate' scope property to show/hide a message and/or disable submission, or whatever you want to do.
Of course it would be better to abstract this validation into a directive so it could easily be reused on all your date fields.
I'm new to angularJS and I'm trying to design a form including 2 input fields which are internally tied to the same scope variable: saying the first field is the speed in km/h and the second field is the speed in m/s. The behavior I'd like to obtain is: whatever field the user changes the other field would be updated.
The problem I'm running into is the following:
When the speed in m/s is changed by the user, the speed in km/h is updated which lead to update again the speed in m/s, which update the speed in km/h until no value change. The side effect is when the speed in m/s is a round number saying 1 and the user change the value to make it decimal entering the decimal dot - the update loop lead to remove the dot because from a calculus point of view 1. is 1.
What is the best way to avoid that?
Here are the fragments of code (html):
<label for="SpeedKm">Speed (Km/h):</label>
<input id="SpeedKm" name="SpeedKm" type="text"
placeholder="km/h"
ng-model="speedKmh"
ng-model-options="{ getterSetter: true}">
>
<label for="SpeedMs">Speed (m/s):</label>
<input id="SpeedMs" name="SpeedMs" type="text"
placeholder="m/s"
ng-model="speedMs"
ng-model-options="{ getterSetter: true}">
>
and the controller
angular.module('speedconverter')
.controller('MainCtrl', ['$scope', function ($scope) {
$scope.speed = 10; //km/h
$scope.speedKmh = function(newValue) {
if (angular.isDefined(newValue)) {
$scope.speed = newValue;
}
return $scope.speed;
};
$scope.speedms = function(newValue) {
if (angular.isDefined(newValue)) {
$scope.speed = newValue*3.6;
}
return $scope.speed/3.6;
};
});
Thank you
I'm trying to create a form of many rows of inputs using ng repeat. Each row has many inputs, with the values the user enters for each row adding up to the a value specific to each row e.g
Value = 20 input 1 = 10, input 2 = 10,
Value = 24 input 1 = 14, input 2 = 10,
From here the user can only submit the form if each line is fully completed and correct. Therefore i want to use ng-show="entireForm.$valid" to display the submit button.
Is this possible? I have been stuck on this problem for a number of days and am completely confused as I am new Angular. As of the minute I have created the view and setup the validator with no logic inside. Due to the amount of opinion out there I'm unsure if this is even possible with ng-repeat
MM.app.directive('lineitemvalidator', function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, elm, attrs, ctrl) {
ctrl.$validators.lineItemValidator = function(modelValue,viewValue) {
var totalInputHrs = parseFloat(scope.childForm.bill.$viewValue) + parseFloat(scope.childForm.drawDown.$viewValue)
+ parseFloat(scope.childForm.carryForward.$viewValue) + parseFloat(scope.childForm.writeOff.$viewValue) + parseFloat(scope.childForm.roundUp.$viewValue);
if((totalInputHrs==parseFloat(scope.lineItem.billableTime))){
/*scope.childForm.bill.$valid=true;
scope.childForm.drawDown.$valid=true;
scope.childForm.carryForward.$valid=true;
scope.childForm.writeOff.$valid=true;
scope.childForm.roundUp.$valid=true;
scope.childForm.drawDown.$setValidity("drawDown", true);
scope.childForm.bill.$setValidity("bill", true);
scope.childForm.carryForward.$setValidity("carryForward", true);
scope.childForm.writeOff.$setValidity("writeOff", true);
scope.childForm.roundUp.$setValidity("roundUp", true);
scope.childForm.$setValidity("childForm",true,scope.childForm);
*/
console.log("Form valid should = true");
console.log(scope.childForm);
return true;
console.log("Form valid = "+scope.childForm.$valid);
}else{
/*scope.childForm.bill.$valid=false;
scope.childForm.drawDown.$valid=false;
scope.childForm.carryForward.$valid=false;
scope.childForm.writeOff.$valid=false;
scope.childForm.roundUp.$valid=false;
scope.childForm.drawDown.$setValidity("drawDown", false);
scope.childForm.bill.$setValidity("bill", false);
scope.childForm.carryForward.$setValidity("carryForward", false);
scope.childForm.writeOff.$setValidity("writeOff", false);
scope.childForm.roundUp.$setValidity("roundUp", false);
scope.childForm.$setValidity("childForm",true,scope.childForm);
*/
console.log("Form valid = "+scope.childForm.$valid);
return false;
console.log("Form valid = "+scope.childForm.$valid);
}
}
}
};
You will have to use ng-form for this scenario. Each form will have some input and validations specific to its input fields. The parent form get automatically marked as $valid or $invalid if any of the child form validation fails.
The setup would look something like this:
<form name='entireForm'>\
<ng-form name='formChild' ng-repeat='item in items'>
<input name='input1' ng-model='item.field1'/>
<input name='input2' ng-model='item.field2'/>
</ng-form>
</form>
Each ng-form can be validated individually and the overall state of the form entireForm is valid only when all children are valid.
I have a directive designed to impose date range restrictions on a date input field (earliest and latest). Here is the directive below, I am using the momentjs library to do my date comparison:
.directive('startDate', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
console.log(arguments);
var compareStartDates = function (value) {
var startDateCompare = moment((attrs.startDate ? scope.$eval(attrs.startDate) : '1901-01-01'), 'YYYY-MM-DD');
if (startDateCompare && startDateCompare.isValid() && value && value.match(/\d{4}\-?\d{2}\-?\d{2}/g)) {
var valueMoment = moment(value, 'YYYY-MM-DD');
if (valueMoment && valueMoment.isValid() && valueMoment < startDateCompare) {
ctrl.$setValidity('startdate', false);
ctrl.$error['startdate'] = true;
return undefined;
}
}
ctrl.$setValidity('startdate', true);
return value;
};
ctrl.$parsers.unshift(compareStartDates);
}
};
})
JSFiddle: http://jsfiddle.net/2ug4X/
Look at the fiddle above and do the following:
1) enter "A" in the text box, the pattern error triggers.
2) click the "CLICK ME" text, which updates teh value of the model on the scope, notice the error clears
3) enter "1800-01-01" in the text box, the date restriction error triggers
4) enter "2000-01-01" in the text box which is a valid date, should clear the startdate error but it doesn't. Any idea why this is happening?
I'd expect updating the ng-model bound variable like so
scope.sample.open_date = '2000-01-01';
would clear the error on the input element like the pattern error clears.
Found this after searching more on Stack: AngularJS custom validation not firing when changing the model programatically
it seems my error was not also pushing the compare function to the ctrl.$formatters like so:
ctrl.$formatters.unshift(compareStartDates);
this did the trick!