Custom directive don't want scope in this how can i achieve? - angularjs

function contentValidator() {
var _matchContent = {
require: 'ngModel',
scope: {
contentValidator: '='
},
link: contentValidatorFn
};
return _matchContent;
function contentValidatorFn(scope, element, attrs, ctrl) {
scope.$watch(function() {
var combined;
if (scope.contentValidator || ctrl.$viewValue) {
combined = scope.contentValidator + '_' + ctrl.$viewValue;
}
return combined;
}, function(value) {
if (value) {
var origin = scope.contentValidator;
if (origin !== ctrl.$viewValue) {
ctrl.$setValidity("contentValidator", false);
return undefined;
} else {
ctrl.$setValidity("contentValidator", true);
return ctrl.$viewValue;
}
}
});
}
}

I'd suggest you do use $validators pipeline to set validity of field of form.
ngModel.$validators.contentValidator = function(modelValue, viewValue) {
var value = modelValue || viewValue;
return condition ? value : undefined; //condition would be what you wanted to check
};
Basically when you return defined value from $validators contentValidator function, but when you don't return angular will add content-validator class on that fields & the same property gets added to that form field like myForm.formFields.$error.contentValidator = true

Related

AngularJS Validation ngModel undefined

I have my form validations dynamically added to the form from a response to a web service call. When the call returns it tells my validation directive what validations I need to add to the form. (I do this because I want to reuse the same validations on the server during submit as I do on the client.) This works wonderfully when the only validations are of type "required". The problem I have is when the model value of the form control does not pass the the validation rules, the model value is then "undefined". Therefore nothing get's sent to the server on form submission to validate on the server side. I do realize I could block the form submission if the form is not valid, however, I am letting the server determine the validity of the data that comes across.
What could I do to force the model value to be the "invalid value" regardless if it violated a validation rule? Better suggestions? Below is a snipit of my directive I am using.
//this directive should be put on an ng-form element and it will hide/show any validations set on each input
.directive('formValidator', ['validatorService', function (vs) {
return {
restrict: 'A',
require: '^form',
link: function (scope, element, attrs, form) {
function iterateOverErrorsObject(errors, func, ignorechecking) {
if (!func)
return;
//show any new errors
for (var key in errors) {
if (key.indexOf('__') == 0)
continue;
_.each(errors[key], function (obj) {
if (form[obj.$name] == obj || ignorechecking) { //ensure the obj is for the current form
var input = vs.findElementByName(element, obj.$name);
if (input.length > 0) {
func(input, obj);
}
}
});
}
}
scope.$watch(function () { return form.$error; }, function (newval, oldval, scp) {
iterateOverErrorsObject(oldval, function (input, obj) {
vs.hideErrors(input);
}, true);
iterateOverErrorsObject(newval, function (input, obj) {
vs.showErrors(input, obj, form._attr);
});
}, true);
//something told the validator to show it's errors
scope.$on('show-errors', function (evt) {
iterateOverErrorsObject(form.$error, function (input, obj) {
vs.showErrors(input, obj, form._attr);
});
});
scope.$on('hide-errors', function (evt) {
vs.hideAllErrors(form);
});
}
};
}])
//this directive is to be put on the ng-form element and will dynamically add/remove validators based on the validations configuration
//which comes back from the service call "Validate"
.directive('dynamicValidators', ['$compile', function ($compile) {
return {
priority: 0,
restrict: 'A',
//require: 'ngModel',
require: '^form',
scope: {
'validations': '=',
},
link: function (scope, element, attrs, ctrl) {
(function (form, scp) {
// this will hold any information necessary to get the error message displayed
// **have to add the form because the ctrl gets recreated every time the form.$error changes
function setAttr(ctrl, key, value) {
if (!ctrl._attr)
ctrl._attr = {};
if (!form._attr)
from._attr = {};
ctrl._attr[key] = value;
var obj = form._attr[ctrl.$name] = {};
obj[key] = value;
};
scope.$watch('validations', function (nv, ov) {
form._attr = {};
//remove old validators
if (ov && ov.length > 0) {
_.each(ov, function (e) {
var fctrl = form[e.MemberNames[0]];
if (fctrl && fctrl.$validators) {
delete fctrl.$validators[e.ErrorKey];
//fctrl.$setValidity(e.ErrorKey, true);
fctrl.$validate();
}
});
}
//add new validators
if (nv && nv.length > 0) {
_.each(nv, function (e) {
var fctrl = form[e.MemberNames[0]];
if (!fctrl)
return;
if (e.ErrorKey == 'required') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
if (modelValue instanceof Array)
return modelValue.length > 0;
else
return modelValue !== '' && modelValue !== null && modelValue !== undefined;
};
} else if (e.ErrorKey == 'alphanumeric') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
return viewValue == null || (viewValue != null && /^[a-zA-Z0-9]*$/.test(modelValue));
};
} else if (e.ErrorKey == 'min') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
return modelValue === undefined || modelValue === null || modelValue === "" || modelValue >= e.ErrorValue;
}
} else if (e.ErrorKey == 'max') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
return modelValue === undefined || modelValue === null || modelValue === "" || modelValue <= e.ErrorValue;
}
}
//make the validator fire to set the status of the validator
if (fctrl.$validators[e.ErrorKey])
//fctrl.$setValidity(e.ErrorKey, fctrl.$validators[e.ErrorKey](fctrl.$modelValue, fctrl.$viewValue))
fctrl.$validate();
});
}
});
})(ctrl, scope);
},
}
}]);
If you still want to send to the server invalid data, you can use the allowInvalid option with the ngModelOptions directive:
<input type="text" name="userName"
ng-model="user.name"
ng-model-options="{ allowInvalid: true }" />
From the documentation for ngModelOptions:
Model updates and validation
The default behaviour in ngModel is that the model value is set to
undefined when the validation determines that the value is invalid. By
setting the allowInvalid property to true, the model will still be
updated even if the value is invalid.

how to execute the statement after promise is executed?

I have used the following directory in my template
i want to change the model value of drop-down to id for it i have used as bellow
<md-select flex class="md-select-form" ng-model="education.degree" placeholder="Degree" save-id id="education.degree_id" ">
<md-option ng-value="degree" ng-repeat="degree in ['High School', 'Associates Degree', 'Bachelor Degree', 'Masters Degree', 'M.B.A', 'M.D', 'Ph.D', 'other']">{{degree}}</md-option>
</md-select>
.directory code
.directive('saveId', function(Profile){
return {
require: 'ngModel',
scope: {
id: '=',
requiredParam:'#'
},
link: function(scope, element, attrs, ngModel) {
console.log("initial loading");
// view --> model (change to string)
ngModel.$parsers.push(function(viewValue){
var keepGoing = true;
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob){
if(keepGoing) {
if(ob.degree == viewValue){
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
console.log("within promise"+scope.id); //it executes second
});
console.log(scope.id); //it executes first
return scope.id;
});
return scope.id;
}
};
})
Once if i try to return the value of ng-model in finally block it also not working
.directive('saveId', function(Profile){
return {
require: 'ngModel',
scope: {
id: '=',
requiredParam:'#'
},
link: function(scope, element, attrs, ngModel) {
console.log("initial loading");
// view --> model (change to string)
ngModel.$parsers.push(function(viewValue){
// var id = -1;
var keepGoing = true;
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob){
if(keepGoing) {
if(ob.degree == viewValue){
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
}).finally(function(res){
console.log(scope.id);
return scope.id;
});
});
return scope.id;
}
};
})
i have used Profile.getDegreeList() service to assign the id to relevant drop-down element by using ngModel.$parsers.push(). The problem is in case of service usage before promise going to complete it returns the previous assigned id .
i want to prevent it and need to return the promise id.
how to solve this issue please help?
You can use finally method which will be executed after executing promise.
Profile.getDegreeList()
.success(function(data)
{
angular.forEach(data, function(ob)
{
if (keepGoing)
if (ob.degree == viewValue) {
scope.id = ob.degree_id;
keepGoing = false;
}
});
console.log("within promise"+scope.id);
})
.finally(function(res)
{
// your code
}
);
Here you go first make a call and then use parsers.
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob) {
if (keepGoing) {
if (ob.degree == viewValue) {
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
ngModel.$parsers.push(function(viewValue) {
var keepGoing = true;
return scope.id ;
});
});
See other links too for more guidance.

How to access an attribute in the directive validator in AngularJS correctly

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

Angular.js: how to test both directive parser and formatter?

I have implemented a simple directive to check username (it should match a regexp, and some more constraint; on blur it's capitalized).
This is my current implementation:
app.directive('checkUserName', function() {
return {
require: 'ngModel',
scope: {
value: '=ngModel'
},
link: function(scope, elm, attrs, model) {
var USERNAME_REGEXP = /^[^_.$\[\]#\/][^.$\[\]#\/]*$/;
model.$parsers.unshift(function(viewValue) {
var user;
var retval;
if (!viewValue) {
model.$setValidity('required', false);
retval = null;
}
if (USERNAME_REGEXP.test(viewValue)) {
if (!user) {
model.$setValidity('taken', true);
model.$setValidity('invalid', true);
retval = viewValue;
} else {
model.$setValidity('taken', false);
model.$setValidity('invalid', true);
}
} else {
model.$setValidity('taken', true);
model.$setValidity('invalid', viewValue === '');
retval = viewValue;
}
return retval;
});
model.$formatters.push(function(modelValue) {
if (modelValue) {
modelValue = capitalize(modelValue);
}
return modelValue;
});
}
};
});
app.directive('renderOn', function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, elm, attrs, ctrl) {
var event = attrs.renderOn;
elm.bind(event, function() {
var viewValue = ctrl.$modelValue;
for (var i in ctrl.$formatters) {
viewValue = ctrl.$formatters[i](viewValue);
}
ctrl.$viewValue = viewValue;
ctrl.$render();
});
}
};
});
function capitalize(text) {
return text.charAt(0).toUpperCase() + text.slice(1);
}
So the question is:
How do I test it?
I did set up a fiddle with the karma test I did write, but the blur handling is obviously wrong...
A secondary question is: is this 'split' implementation (checkUserName + renderOn) advisable strategy when implementing both 'parsing' and 'formatting' directives?

AngularJS custom validation, dates are either before or after other date

I have these custom validation directives which work as intended.
Problems is I want to trigger validation of the input when the other input that is used to compare with is changed.. Is this solvable in the watch block?
Lets say i have
<input afterOtherDate="dateB" ng-model="dateA" value="2014"/>
<input beforeOtherDate="dateA" ng-model="dateB" value="2013"/>
If i then set dateA to be after dateB, dateA will become invalid, but dateB wont know.
Same the other way around, if i have
<input afterOtherDate="dateB" ng-model="dateA" value="2013"/>
<input beforeOtherDate="dateA" ng-model="dateB" value="2014"/>
Both inputs need to be re-validated when the other one changes. So both become valid.
validationHelpers.directive('afterOtherDate', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var doValidation = function () {
ctrl.$parsers.unshift(function (viewValue) {
if (viewValue <= compareTo) {
ctrl.$setValidity('afterOtherDate', false);
return viewValue;
} else {
ctrl.$setValidity('afterOtherDate', true);
return viewValue;
}
});
};
var scopeHierarchy = attrs["afterOtherDate"].split('.');
var compareTo = scope;
for (var k = 0; k < scopeHierarchy.length; k++) {
compareTo = compareTo[scopeHierarchy[k]];
}
scope.$watch(attrs["afterOtherDate"], function (val) {
});
doValidation();
}
};
});
validationHelpers.directive('beforeOtherDate', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var doValidation = function () {
ctrl.$parsers.unshift(function (viewValue) {
if (viewValue <= compareTo) {
ctrl.$setValidity('beforeOtherDate', true);
return viewValue;
} else {
ctrl.$setValidity('beforeOtherDate', false);
return viewValue;
}
});
};
var scopeHierarchy = attrs["beforeOtherDate"].split('.');
var compareTo = scope;
for (var k = 0; k < scopeHierarchy.length; k++) {
compareTo = compareTo[scopeHierarchy[k]];
}
scope.$watch(attrs["beforeOtherDate"], function (val) {
});
doValidation();
}
};
});
Would be cool to solve it inside the custom directives!
BR
twd
What I do in my directive, that I get the controller of ngModel of the field which I'm trying to compare to:
var compareModel = scope.$eval([formCtrl.$name, attr.dateAfter].join("."));
I'm passing the model name in the attribute. Then you can access its parsers and push a method that would fire a validation in your model.
compareModel.$parsers.push(function (value) {
I force the compareModel to revalidate by using $setViewValue:
compareModel.$parsers.push(function (value) {
checkBefore(value);
model.$setViewValue(model.$viewValue);
return value;
});

Resources