I have used following code for directive which compares two dates (reference Custom form validation directive to compare two fields)
define(['./module'], function(directives) {
'use strict';
directives.directive('lowerThan', [
function() {
var link = function($scope, $element, $attrs, ctrl) {
ctrl.$setValidity('lowerThan', false);
var validate = function(viewValue) {
var comparisonModel = $attrs.lowerThan;
/*if(!viewValue || !comparisonModel){
// It's valid because we have nothing to compare against
//console.log("It's valid because we have nothing to compare against");
ctrl.$setValidity('lowerThan', true);
}*/
// It's valid if model is lower than the model we're comparing against
//ctrl.$setValidity('lowerThan', parseInt(viewValue, 10) < parseInt(comparisonModel, 10) );
if(comparisonModel){
var to = comparisonModel.split("-");
var t = new Date(to[2], to[1] - 1, to[0]);
}
if(viewValue){
var from=viewValue.split("-");
var f=new Date(from[2],from[1]-1,from[0]);
}
console.log(Date.parse(t)>Date.parse(f));
ctrl.$setValidity('lowerThan', Date.parse(t)>Date.parse(f));
return viewValue;
};
ctrl.$parsers.unshift(validate);
ctrl.$formatters.push(validate);
$attrs.$observe('lowerThan', function(comparisonModel){
// Whenever the comparison model changes we'll re-validate
return validate(ctrl.$viewValue);
});
};
return {
require: 'ngModel',
link: link
};
}
]);
});
but when page is loaded first time it displays error message. i have tried using ctrl.$setValidity('lowerThan', false); to make it invisible first time. but it is not working.
Here is plunker for the same.
http://plnkr.co/edit/UPN1g1JEoQMSUQZoCDAk?p=preview
Your directive is fine. You're setting your date values inside the controller, and you're setting the lower date to a higher value, which means the dates are invalid on load. Your directive correctly detects that. If you don't want your directive to validate your data on load, than you'll need three things:
Remove the $attrs.$observe
Create and apply a higherThan directive to the other field
Tell your directive not to apply to the model value ($formatters array) but only to the input value ($parsers array).
PLUNKER
'use strict';
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
$scope.field = {
min: "02-04-2014",
max: "01-04-2014"
};
});
app.directive('lowerThan', [
function() {
var link = function($scope, $element, $attrs, ctrl) {
var validate = function(viewValue) {
var comparisonModel = $attrs.lowerThan;
var t, f;
if(!viewValue || !comparisonModel){
// It's valid because we have nothing to compare against
ctrl.$setValidity('lowerThan', true);
}
if (comparisonModel) {
var to = comparisonModel.split("-");
t = new Date(to[2], to[1] - 1, to[0]);
}
if (viewValue) {
var from = viewValue.split("-");
f = new Date(from[2], from[1] - 1, from[0]);
}
ctrl.$setValidity('lowerThan', Date.parse(t) > Date.parse(f));
// It's valid if model is lower than the model we're comparing against
return viewValue;
};
ctrl.$parsers.unshift(validate);
//ctrl.$formatters.push(validate);
};
return {
require: 'ngModel',
link: link
};
}
]);
app.directive('higherThan', [
function() {
var link = function($scope, $element, $attrs, ctrl) {
var validate = function(viewValue) {
var comparisonModel = $attrs.higherThan;
var t, f;
if(!viewValue || !comparisonModel){
// It's valid because we have nothing to compare against
ctrl.$setValidity('higherThan', true);
}
if (comparisonModel) {
var to = comparisonModel.split("-");
t = new Date(to[2], to[1] - 1, to[0]);
}
if (viewValue) {
var from = viewValue.split("-");
f = new Date(from[2], from[1] - 1, from[0]);
}
ctrl.$setValidity('higherThan', Date.parse(t) < Date.parse(f));
// It's valid if model is higher than the model we're comparing against
return viewValue;
};
ctrl.$parsers.unshift(validate);
//ctrl.$formatters.push(validate);
};
return {
require: 'ngModel',
link: link
};
}
]);
<form name="form" >
Min: <input name="min" type="text" ng-model="field.min" lower-than="{{field.max}}" />
<span class="error" ng-show="form.min.$error.lowerThan">
Min cannot exceed max.
</span>
<br />
Max: <input name="max" type="text" ng-model="field.max" higher-than="{{field.min}}" />
<span class="error" ng-show="form.max.$error.higherThan">
Max cannot be lower than min.
</span>
</form>
Related
I implemented custom directive for if select element, in which if there is only one item then it selects that item by default else it works as regular drop down which works fine.
My problem is I have two drop down as cascade. A Company list and a Department list. The Department list depends on a Company list. So when only one Company is selected by default then Department list will also update respectively by default.
For that I try to use ng-change but It does not access updated value it shows old value in log.
My angularjs code is below:
angular.module('TestApp', [
]);
angular.module('TestApp').directive('advanceDropdown', function () {
var directive = {}
directive.restrict = 'A';
directive.require = 'ngModel';
directive.scope = {
items: '=advanceDropdown',
model: '=ngModel',
key: '#key',
change: '&ngChange'
}
directive.link = function (scope, element, attrs, ctrl) {
scope.$watch('items', function (n, o) {
if (scope.items.length == 1) {
scope.model = scope.items[0][scope.key];
scope.change();
}
});
}
return directive;
});
(function () {
'use strict';
angular.module('TestApp').controller('TestCtrl', ['$scope', TestCtrl]);
function TestCtrl($scope) {
$scope.departmentList = [];
$scope.companyList = [];
$scope.designation = new Designation();
$scope.active = null;
$scope.BindDepartments = function () {
console.log('updated companyId:' + $scope.designation.CompanyId);
//Code here
//Load department base on selected company
//$scope.departmentList = data;
}
$scope.GetCompanyById = function (companyId) {
//Get company data by id
//recived from server
$scope.companyList = [{CompanyId:2,CompanyName:'xyz'}];
}
$scope.Init = function () {
$scope.active = null;
$scope.designation = new Designation();
$scope.GetCompanyById(2);
}
}
})();
function Designation() {
this.DesignationId = 0;
this.DesignationName = '';
this.DepartmentId = 0;
this.CompanyId = 0;
}
Designation.prototype = {
constructor: Designation
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular.min.js"></script>
<div ng-app="TestApp" ng-controller="TestCtrl">
<select ng-model="designation.CompanyId"
ng-options="dept.CompanyId as dept.CompanyName for dept in companyList"
ng-change="BindDepartments()"
advance-dropdown="companyList" key="CompanyId" required>
<option value="" disabled>--Select Company--</option>
</select>
</div>
In a BindDepartment() method $scope.designation.CompanyId value was not updated.
It Show log updated company id:0
I expect updated company id:2
Plunker demo
Avoid using isolate scope with the ng-model controller.
//directive.scope = {
// items: '=advanceDropdown',
// model: '=ngModel',
// key: '#key',
// change: '&ngChange'
//}
directive.scope = false;
Instead use the $setViewValue method to change the model value.
directive.link = function (scope, element, attrs, ctrl) {
scope.$watchCollection(attrs.advanceDropdown, function (n, o) {
if (n && n.length == 1) {
ctrl.$setViewValue(n[0]);
}
});
}
The $setViewValue method will automatically invoke the ng-change expression.
Apologies in advance, directives are not my strong suit!
I have a simple attribute-only directive, the purpose of which is to automatically convert a string in a field to an HH:mm format upon blur'ing the field. This is the directive:
(function () {
'use strict';
angular
.module('app.format-as-time')
.directive('formatAsTime', timeDirective);
timeDirective.$inject = [
'isValid'
];
function timeDirective (isValid) {
return {
require: 'ngModel',
restrict: 'A',
link: LinkFunction
};
function LinkFunction (scope, elem, attrs, ngModel) {
elem.bind('blur', function () {
var currentVal = ngModel.$modelValue,
formattedVal = '';
// Format something like 0115 to 01:15
if (currentVal.length === 4) {
formattedVal = currentVal.substr(0, 2) + ':' + currentVal.substr(2, 2);
// Format something like 115 to 01:15
} else if (currentVal.length === 3) {
formattedVal = '0' + currentVal.substr(0, 1) + ':' + currentVal.substr(1, 2);
// Format something like 15 to 00:15
} else if (currentVal.length === 2) {
formattedVal = '00:' + currentVal;
}
// If our formatted time is valid, apply it!
if (isValid.time(formattedVal)) {
scope.$applyAsync(function () {
ngModel.$viewValue = formattedVal;
ngModel.$render();
});
}
});
}
}
}());
And the associated view:
<div ng-controller="TestController as test">
<input type="text"
maxlength="5"
placeholder="HH:mm"
ng-model="test.startTime"
format-as-time>
<button ng-click="test.getStartTime()">Get Start Time</button>
</div>
And the associated Controller:
(function () {
'use strict';
angular
.module('app.testModule')
.controller('TestController', TestController);
function TestController () {
var vm = this;
vm.startTime = '';
vm.getStartTime = function () {
console.log(vm.startTime);
}
}
}());
At present, the directive works as expected for the view but the model in my controller does not get updated, i.e. the input will contain 01:15 but the model will console.log() 115.
I have tried using:
scope: {
ngModel: '='
}
in the directive but this did not do anything.
Have I done this the right way, and if so what do I need to add to ensure both the model and view remain in sync?
If I have done this the wrong way, what would be the best way to do it correctly?
The problem lies in this line ngModel.$viewValue = formattedVal; Angular has a pipeline used to set a modelValue which includes running it through registered $parsers and $validators. The proper way to set the value is by calling $setViewValue(formattedVal) which will run the value through this pipeline.
I'm making a validator which validates valid dates like MM/YYYY, but I didn't get how to access an attribute when the model changes:
<input id="my-date"
validate-short-date
data-max-date="{{thisMonth}}"
type="text"
name="myDate"
data-ng-model="myModelDate">
Here is the directive
.directive('validateShortDate', ['moment', function(moment) {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, element, attr, ngModel) {
var maxDate = false;
var pattern, regex;
pattern = '^((0[0-9])|(1[0-2])|[1-9])\/(19|20)[0-9]{2}$';
regex = new RegExp(pattern, 'i');
if(!angular.isUndefined(attr.maxDate)) {
// GOT ONLY ONCE
maxDate = attr.maxDate;
}
ngModel.$validators.maxDate = function(modelValue) {
// maxDate var is undefined after the first time
if (maxDate && regex.test(modelValue)) {
var modelDate = moment(modelValue, 'MM/YYYY').format('YYYYMM');
return modelDate <= maxDate;
}
return true;
};
ngModel.$validators.valid = function(modelValue) {
return modelValue === '' || modelValue === null || angular.isUndefined(modelValue) || regex.test(modelValue);
};
}
};
}])
The validator ngModel.$validators.valid works perfect, but inside ngModel.$validators.maxDate i cannot get the attr.maxDate but the first time directive fires.
So how can I access to a custom attribute value every time I check the modelValue?
I'm not an expert with AngularJS and probably I'm missing something important.
The attrs argument in the link function provides you with a $observe method which you can use to attach a listener function for dynamic changes in an attribute value.
It is very simple to use inside of your link function:
attr.$observe('maxDate', function() {
scope.maxDate = attr.maxDate;
ngModel.$validate();
});
Here is a working Plunker
You can do like this for track the change in ng-model:-
HTML
<input id="my-date"
validate-short-date
data-max-date="{{thisMonth}}"
type="text"
name="myDate"
data-ng-model="myModelDate">
Angularjs code:-
app.directive('validateShortDate', ['moment', function(moment) {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, element, attr, ngModel) {
var maxDate = false;
var pattern, regex;
pattern = '^((0[0-9])|(1[0-2])|[1-9])\/(19|20)[0-9]{2}$';
regex = new RegExp(pattern, 'i');
if(!angular.isUndefined(attr.maxDate)) {
// GOT ONLY ONCE
maxDate = attr.maxDate;
}
ngModel.$validators.maxDate = function(modelValue) {
// maxDate var is undefined after the first time
if (maxDate && regex.test(modelValue)) {
var modelDate = moment(modelValue, 'MM/YYYY').format('YYYYMM');
return modelDate <= maxDate;
}
return true;
};
ngModel.$validators.valid = function(modelValue) {
return modelValue === '' || modelValue === null || angular.isUndefined(modelValue) || regex.test(modelValue);
};
}
$scope.$watch('ngModel',function(){
console.log(attr.dataMaxDate);
});
};
}])
I have a range input (between [lowerValue] and [upperValue]) on a form and I want to make a reusable directive called 'validateGreaterThan' that can be attached to any form and use the ngModel $validators functionality so I can attach multiple ones onto an input.
You can check a simple demo on jsbin here:
http://jsbin.com/vidaqusaco/1/
I've set up a directive called nonNegativeInteger and that works correctly, however, the validateGreaterThan directive I have isn't working. How can I get it to reference the lowerValue?
I appreciate any help with this.
Here is the basic idea:-
Define 2 directives and let each directive refer to the other field buy passing its name. When the validator runs on the current field you can retrieve the model value of another field and now you have values from both fields and you can ensure the relation between the 2 fields.
As per my code below I have 2 fields minimumAmount and maximumAmount where the minimumAmount cannot be greater than the maximum amount and vice-versa.
<input name="minimumAmount" type="number" class="form-control"
ng-model="entity.minimumAmount"
less-than-other-field="maximumAmount" required/>
<input name="maximumAmount" type="number"
ng-model="entity.maximumAmount"
greater-than-other-field="minimumAmount"
class="form-control"/>
Here we have 2 directives lessThanOtherField and greaterThanOtherField and they both refer to other field as we pass the other field name. greater-than-other-field="minimumAmount" we are passing the other field.
.directive('lessThanOtherField', ['$timeout',function($timeout){
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var xFieldValidatorName = 'lessThanOtherField';
var form = elm.parent().controller('form');
var otherFieldName = attrs[xFieldValidatorName];
var formFieldWatcher = scope.$watch(function(){
return form[otherFieldName];
}, function(){
formFieldWatcher();//destroy watcher
var otherFormField = form[otherFieldName];
var validatorFn = function (modelValue, viewValue) {
var otherFieldValue = otherFormField.hasOwnProperty('$viewValue') ? otherFormField.$viewValue : undefined;
if (angular.isUndefined(otherFieldValue)||otherFieldValue==="") {
return true;
}
if (+viewValue < +otherFieldValue) {
if (!otherFormField.$valid) {//trigger validity of other field
$timeout(function(){
otherFormField.$validate();
},100);//avoid infinite loop
}
return true;
} else {
// it is invalid, return undefined (no model update)
//ctrl.$setValidity('lessThanOtherField', false);
return false;
}
};
ctrl.$validators[xFieldValidatorName] = validatorFn;
});
}
};
}])
.directive('greaterThanOtherField', ['$timeout',function($timeout){
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var xFieldValidatorName = 'greaterThanOtherField';
var form = elm.parent().controller('form');
var otherFieldName = attrs[xFieldValidatorName];
var formFieldWatcher = scope.$watch(function(){
return form[otherFieldName];
}, function(){
formFieldWatcher();//destroy watcher
var otherFormField = form[otherFieldName];
var validatorFn = function (modelValue, viewValue) {
var otherFieldValue = otherFormField.hasOwnProperty('$viewValue') ? otherFormField.$viewValue : undefined;
if (angular.isUndefined(otherFieldValue)||otherFieldValue==="") {
return true;
}
if (+viewValue > +otherFieldValue) {
if (!otherFormField.$valid) {//trigger validity of other field
$timeout(function(){
otherFormField.$validate();
},100);//avoid infinite loop
}
return true;
} else {
// it is invalid, return undefined (no model update)
//ctrl.$setValidity('lessThanOtherField', false);
return false;
}
};
ctrl.$validators[xFieldValidatorName] = validatorFn;
});
}
};
}])
We have requirement to show a drop down when user enters a "#".
I am planning to have a directive as following:
app.controller('MainCtrl', function($scope) {
$scope.values = ['#'];
$scope.valuesEntered = false;
});
app.directive('identifier', function ($parse) {
return {
scope: {
values: '=values'
},
link: function (scope, elm, attrs) {
elm.bind('keypress', function(e){
var char = String.fromCharCode(e.which||e.charCode||e.keyCode), matches = [];
angular.forEach(scope.values, function(value, key){
if(char === value) matches.push(char);
}, matches);
if(matches.length !== 0){
$scope.valuesEntered = true;
}
});
}
}
});
Will this be ok ?
Here is a simple directive I made that will allow you to specify an expression to evaluate when a given key is pressed or one of an array of keys is pressed.
Note that this is a one-way street. There is currently no going back once you have detected that keypress, even if the user pressed backspace.
var app = angular.module('sample', []);
app.controller('mainCtrl', function($scope) {
$scope.values = ['#', '!'];
$scope.valuesEntered = false;
$scope.valuesEntered2 = false;
});
app.directive('whenKeyPressed', function($parse) {
return {
restrict: 'A',
scope: {
action: '&do'
},
link: function(scope, elm, attrs) {
var charCodesToMatch = [];
attrs.$observe('whenKeyPressed', function(keys) {
if (angular.isArray(keys))
charCodesToMatch = keys.map(function(key) {
if (angular.isString(key))
return key.charCodeAt(0);
});
else if (angular.isString(keys))
charCodesToMatch = keys.split('').map(function(ch) {
return ch.charCodeAt(0);
});
});
elm.bind('keypress', function(e) {
var charCode = e.which || e.charCode || e.keyCode;
if (charCodesToMatch.indexOf(charCode) > -1)
scope.action();
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="sample">
<div ng-controller="mainCtrl">
<p>Values "#" entered? {{valuesEntered}}</p>
<textarea ng-model="str" when-key-pressed="#" do="valuesEntered = true"></textarea>
<p>Values {{values}} entered 2: {{valuesEntered2}}</p>
<textarea ng-model="str2" when-key-pressed="{{values}}" do="valuesEntered2 = true"></textarea>
</div>
</div>
Plunkr demo