Using element.bind in validation directive - angularjs

I trying to validate input with custom directive:
.directive('customValidation', function () {
return {
require: 'ngModel',
link: function (scope, element, attr, ngModelCtrl) {
function fromUser(text) {
element.bind("keydown keypress", function (event) {
if (!(event.keyCode >= 48 && event.keyCode <= 57)) {
return undefined;
}
})
}
ngModelCtrl.$parsers.push(fromUser);
}
};
});
but it doesn't work. Any character is passes validation. What I doing wrong?

So basically what you are trying to achieve is to check if an input contains only numbers. At least that is what I can understand from your explanation and the sample code.
First, you are using a parser, which are used for sanitizing and converting values passed from the DOM to the model. Validation comes afterwards. If you just want to check if only numbers are written, then you need something like this:
ngModel.$validators.validCharacters = function(modelValue, viewValue) {
var value = modelValue || viewValue;
return /[0-9]+/.test(value);
};
I suggest reading the API docs as they explain all ngModelController functionality very thoroughly: click here for thorough docs
Second, you are binding to a event everytime your parser is called. The parser is called each time you change the contents of your input element. If you type the word everytime into your input, you end up binding the event nine times! Apart from the fact that binding to the event after you changed something is too late as your first event was already fired.

Related

Angular parser called on blur

I have a type of input mask using an angular directive. I'm using a formatters and the blur event to format the model value for display, and I'm using parsers and the focus event to remove the formatting when the user edits the textbox.
I'm getting strange behaviour in Internet Explorer where if you use the Tab key to lose focus, the parser event is (incorrectly) firing so the model value is being updated incorrectly.
Is this an angular bug? Or is there something I'm doing wrong?
Here is a fiddle: https://jsfiddle.net/capesean/htorwgs5/3/
Note that in IE, with your console window open, you will see the events logging out.
Also, testing this on an earlier Angular version, seems to work fine:
https://jsfiddle.net/htorwgs5/4/
The directive code is:
.directive("test", function () {
return {
restrict: "A",
require: 'ngModel',
link: function (scope, element, attr, ngModel) {
// for DOM -> model validation
ngModel.$parsers.unshift(function (value) {
console.log("parser");
ngModel.$setValidity('test', true);
return +value;
});
ngModel.$formatters.unshift(function (value) {
console.log("formatter");
ngModel.$setValidity('test', true);
return (value === undefined ? "" : value) + "!";
});
element.val(scope.minutes);
element.bind("blur", function () {
scope.$apply(function () {
console.log("blur");
element.val((scope.minutes === undefined ? "" : scope.minutes) + "#");
});
});
element.bind("focus", function () {
scope.$apply(function () {
console.log("focus");
element.val(scope.minutes);
});
});
}
};
})
This is known behaviour. I've posted a bug report here:
https://github.com/angular/angular.js/issues/14987
The solution was to use $timeout to delay the setting of the element value, as suggested in the reply to the bug report.

Toggle edit and display of the fields in a form

Above all, I have the plnkr at here.
I am trying to create a series of directive that support in-place toggle of text display and edit within a form. As I understand, there is a similar module like xeditable available, but we need to do something different down the road. So I started with an experiment to start with something similar.
First, I create a directive that allows toggling edit/display by setting an attribute editEnabled on the directive called editableForm. The following code does not do anything special other than a line of log message.
function editableForm ($log) {
var directive = {
link: link,
require: ['form'],
restrict: 'A',
scope: {
editEnabled: "&editEnabled"
}
};
return directive;
function link(scope, element, attrs, controller) {
//$log.info('editEnabled: ' + scope.editEnabled());
$log.info('editEnabled: ' + attrs.editEnabled); //this also works
}
} //editableForm
Then I wrote the following directive to override the input tag in html:
//input directive
function input($log) {
var directive = {
link: link,
priority: -1000,
require: ['^?editableForm', '?ngModel'],
restrict: 'E'
};
return directive;
function link(scope, element, attrs, ngModel) {
ngModel.$render = function() {
if (!ngModel.$viewValue || !ngModel.$viewValue) {
return;
}
element.text(ngModel.$viewValue);
};
$log.info('hello from input');
$log.info('input ngModel: ' + attrs.ngModel);
// element.val('Hello');
scope.$apply(function() {
ngModel.$setViewValue('hello');
ngModel.$render();
});
}
} //input
I was trying to show the ngModel value of the input as text in the input directive, however, it doesn't seem to do anything in my testing. Could someone spot where I am doing wrong? I wish to replace each input fields with text/html (e.g. <span>JohnDoe</span> for Username).
My first attempt on input is a proof of concept. If it works, I will keep working on other tags like button, select, etc.
Long shot here... Your requiring both editableForm and ngModel in your input directive. So the fourth parameter of your link function should be an array of controllers in the respective order of the require array, not the ngModel controller as you are expecting.
I didnt go any further in examining your code, but check it out.

Force validation on submit

I'm validating an input element with the directive below. The problem is that this way it's only executed when the input element is activated. Is there a way to force execute the parsers methods of all input elements of a form?
"use strict";
angular.module("kap.directive")
.directive('kapValidationDuration', ['$timeout', '$log', function ($timeout, $log) {
return {
restrict: 'A',
require: 'ngModel',
scope: { minReservationDurationMinutes: '=minReservationDurationMinutes' },
link: function (scope, element, attrs, ctrl) {
if (attrs.type === 'radio' || attrs.type === 'checkbox') {
return;
}
ctrl.$parsers.push(function (value) {
if(value && !element[0].hidden) {
var lTimeValues = value.split(":"),
lHoursToMinutes = parseInt(lTimeValues[0], 10) * 60,
lMinutes = parseInt(lTimeValues[1], 10),
lMinReservationDurationMinutes = parseInt(attrs.minreservationdurationminutes, 10) || 10,
lValidity = true;
if ((lHoursToMinutes + lMinutes) < lMinReservationDurationMinutes) {
lValidity = false;
}
ctrl.$setValidity('kapValidationDuration', lValidity);
}
return value;
});
}
};
}]);
In order to do that, i.e. validate the initial values as well, you also have to use the $formatters. Luckilly, in your case, you just have to unshift the same function to the $formatters, as the one used for the $parsers.
The reason is that the parsers are used when going form → model. In general this means data conversion: if the model was a number, the input from the user is always a string and has to be converted; the errors may not only be validation (e.g. "age must be positive") but also parsing (e.g. "'tata' is not a valid number"). The formatters when going model → form. This is, as the name implies, formatting: e.g. a Date object may need to be displayed as dd/MM/yyyy in my locale (greek), but MM/dd/yyyy in other locales.
See this fiddle: http://jsfiddle.net/52dAB/
In the fiddle I am still using two separate functions for the formatters and the parsers, despite them being identical in implementation. Just for the sake of generality.

AngularJS - In a directive that changes the model value, why do I have to call $render?

I made a directive designed to be attached to an element using the ngModel directive. If the model's value matches something the value should then set to the previous value. In my example I'm looking for "foo", and setting it back to the previous if that's what's typed in.
My unit tests passed fine on this because they're only looking at the model value. However in practice the DOM isn't updated when the "put back" triggers. Our best guess here is that setting old == new prevents a dirty check from happening. I stepped through the $setViewValue method and it appears to be doing what it ought to. However it won't update the DOM (and what you see in the browser) until I explicitly call ngModel.$render() after setting the new value. It works fine, but I just want to see if there's a more appropriate way of doing this.
Code is below, here's a fiddle with the same.
angular.module('myDirective', [])
.directive('myDirective', function () {
return {
restrict: 'A',
terminal: true,
require: "?ngModel",
link: function (scope, element, attrs, ngModel) {
scope.$watch(attrs.ngModel, function (newValue, oldValue) {
//ngModel.$setViewValue(newValue + "!");
if (newValue == "foo")
{
ngModel.$setViewValue(oldValue);
/*
I Need this render call in order to update the input box; is that OK?
My best guess is that setting new = old prevents a dirty check which would trigger $render()
*/
ngModel.$render();
}
});
}
};
});
function x($scope) {
$scope.test = 'value here';
}
Our best guess here is that setting old == new prevents a dirty check from happening
A watcher listener is only called when the value of the expression it's listening to changes. But since you changed the model back to its previous value, it won't get called again because it's like the value hasn't changed at all. But, be careful: changing the value of a property inside a watcher monitoring that same property can lead to an infinite loop.
However it won't update the DOM (and what you see in the browser) until I explicitly call ngModel.$render() after setting the new value.
That's correct. $setViewValue sets the model value as if it was updated by the view, but you need to call $render to effectively render the view based on the (new) model value. Check out this discussion for more information.
Finally, I think you should approach your problem a different way. You could use the $parsers property of NgModelController to validate the user input, instead of using a watcher:
link: function (scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$parsers.unshift(function(viewValue) {
if(viewValue === 'foo') {
var currentValue = ngModel.$modelValue;
ngModel.$setViewValue(currentValue);
ngModel.$render();
return currentValue;
}
else
return viewValue;
});
}
I changed your jsFiddle script to use the code above.
angular.module('myDirective', [])
.directive('myDirective', function () {
return {
restrict: 'A',
terminal: true,
require: "?ngModel",
link: function (scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$parsers.unshift(function(viewValue) {
if(viewValue === 'foo') {
var currentValue = ngModel.$modelValue;
ngModel.$setViewValue(currentValue);
ngModel.$render();
return currentValue;
}
else
return viewValue;
});
}
};
});
function x($scope) {
$scope.test = 'value here';
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<h1>Foo Fighter</h1>
I hate "foo", just try and type it in the box.
<div ng-app="myDirective" ng-controller="x">
<input type="text" ng-model="test" my-directive>
<br />
model: {{test}}
</div>

Angular - Directive reflecting ngModel array

This is piggy-backing a little off an earlier question. I have been trying to work with directives and data from a model/array
My model looks something like this:
$scope.testModel = {
inputA:[1,2,3]
};
Then I would have inputs for each.
My directive is checking, on keyup, if the input is greater than a given number (10). If it is, then it sets it as 10.
link: function(scope, element) {
scope.$watch('ngModel',function(val){
element.val(scope.ngModel);
});
element.bind("keyup", function(event) {
if(parseInt(element.val())>10){
element.val(10);
scope.ngModel=element.val();
scope.$apply();
}
});
}
The problem is that I get an error, depending on the input:
TypeError: Cannot set property '0' of undefined
Here is the fiddle to see the error and code I have set up: https://jsfiddle.net/prXm3/3/
NOTE
I would prefer not to change the data set as I receive it directly from the server. I know I can change the model to inputA0:1,inputA1:2,inputA2:3 and get it to work, but then I would have to transform the data when my app gets it, and then re-transform it when I send to back to the server. I would prefer to leave the model as I have it set.
Since your directive is interacting with ngModel, you should work along with it in order to update both the model and the view:
angular.module('test', []).directive('myDirective', function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$parsers.unshift(function(viewValue) {
if(parseInt(viewValue) > 10) {
ngModel.$setViewValue(10);
ngModel.$render();
return 10;
}
else
return viewValue;
});
}
}
});
Working jsFiddle.
Perhaps you'd be interested in checking out the following posts for further information:
NgModelController
Developer's Guide on Forms

Resources