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

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?

Related

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

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

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.

angularjs: how to know all directive is ready

I want make sure all directives are ready. How to? Here is what I try, but not correct.
leafUi.factory('leafState', function($rootScope) {
var unreadyUi = [], readyUi = [];
return {
unready: function(ui) {
console.log(ui + ' unready');
unreadyUi.push(ui);
},
ready: function(ui) {
console.warn(ui + ' ready');
readyUi.push(ui);
if (readyUi.length === unreadyUi.length) {
$rootScope.$broadcast('leafUiReady', 'all leaf ui component is ready');
// unreadyUi = null;
// readyUi = null;
}
}
}
});
leafUi.directive('leafScroll', function($timeout, leafState, leafScroll) {
return {
restrict: 'E',
transclude: true,
link: function(scope, ele, attrs, ctrl, transclude) {
leafState.unready('leafScroll');
// more code .....
leafState.ready('leafScroll');
}
};
});
leafUi.directive('leafContent', function($timeout, leafState, leafScroll) {
return {
restrict: 'E',
transclude: true,
link: function(scope, ele, attrs, ctrl, transclude) {
leafState.unready('leafContent');
// more code .....
leafState.ready('leafContent');
}
};
});
// more directive.....
Here is an example log:
From the log, we can know that directive ready and unready can be separated by other directive and length of readyUi has many chance to equal to unreadyUi.
So how can I assure all directives are ready?
Perhaps add another condition to what you define as leafUiReady could help you. Since the directives are loaded almost synchronously you get the logic that your undreadyUi and readyUi are almost always the same length during compilation.
Perhaps add logic like this to your leafState factory where you also look at the DOM ready-function.
leafUi.factory('leafState', function($rootScope) {
var unreadyUi = [], readyUi = [];
var domReady = false;
angular.element(document).ready(function () {
domReady = true;
if (readyUi.length === unreadyUi.length) {
$rootScope.$broadcast('leafUiReady', 'all leaf ui component is ready');
// unreadyUi = null;
// readyUi = null;
}
});
return {
unready: function(ui) {
console.log(ui + ' unready');
unreadyUi.push(ui);
},
ready: function(ui) {
console.warn(ui + ' ready');
readyUi.push(ui);
if (domReady && readyUi.length === unreadyUi.length) {
$rootScope.$broadcast('leafUiReady', 'all leaf ui component is ready');
// unreadyUi = null;
// readyUi = null;
}
}
}
});
Perhaps change some other logic to make this code a bit cleaner.

Creating directive that manipulates the ngmodel value

I am trying to create a directive that does the following:
Has a default value when the input is empty.
Limits the input to a upper and lower bounds, when it exeeds these it is specifically set to the upper or lower it exceeded.
Here is my code using the link function:
app.directive('rangedInput', function ($parse) {
return {
scope: {
lower: "#",
upper: "#",
},
require: 'ngModel',
link:
function ($scope, element, attrs, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
element.bind('blur', function (event) {
event.preventDefault();
var value = parseInt(element.val());
var upper = parseInt($scope.upper);
var lower = parseInt($scope.lower);
if (value > upper) {
value = upper;
} else if (value < lower) {
value = lower;
}
$scope.$apply(function () {
ngModelCtrl.$setViewValue(value);
ngModelCtrl.$render();
});
});
}
};
});
And the usage:
<label>{{model.Amount}}</label>
<input type="text" upper="1500000" lower="50000" ranged-input ng-model="model.Amount" />
I also tried one using the compile function:
app.directive('rangedInput', function ($parse) {
return {
scope: {
lower: "#",
upper: "#",
},
require: 'ngModel',
compile: function () {
var getter, setter;
return function ($scope, element, attrs, ngModelCtrl) {
getter = $parse(attrs.ngModel);
setter = getter.assign;
if (!ngModelCtrl) {
return;
}
element.bind('blur', function (event) {
event.preventDefault();
var value = parseInt(element.val());
var upper = parseInt($scope.upper);
var lower = parseInt($scope.lower);
if (value > upper) {
value = upper;
}
else
if (value < lower) {
value = lower;
}
element[0].value = value;
$scope.$apply(function () {
setter($scope, value);
});
});
};
}
};
});
Neither work as desired, the problem that I am finding is that the value of ng-model="model.Amount" doesn't seem to get updated correctly?
Here is the fixed code. Check this out.
app.directive('rangedInput', function ($parse) {
return {
scope: {
lower: "#",
upper: "#",
},
require: 'ngModel',
link:
function ($scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}
element.bind('blur', function (event) {
event.preventDefault();
var value = parseInt(element.val());
var upper = parseInt($scope.upper);
var lower = parseInt($scope.lower);
if (value > upper) {
value = upper;
} else if (value < lower) {
value = lower;
}
$scope.$apply(function () {
ngModel.$setViewValue(value);
ngModel.$render();
});
});
}
};
});

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