angularjs: parsing date input - javascript

I need a directive which will parse user input to date and validate it. So I wrote the following:
myDirectives.directive('myDate', function($filter) {
'use strict';
return {
require:'ngModel',
restrict:'A',
link:function (scope, elem, attrs, ctrl) {
var dateFormat = attrs.myDate ? attrs.myDate : 'shortDate';
ctrl.$formatters.unshift(function(modelValue) {
return $filter('date')(modelValue, dateFormat);
});
ctrl.$parsers.unshift(function(viewValue) {
var date = new Date(viewValue);
if (isNaN(date)) {
ctrl.$setValidity('date', false);
return undefined;
} else {
var dateString = $filter('date')(date, dateFormat);
if (dateString !== viewValue) {
ctrl.$setViewValue(dateString);
}
ctrl.$setValidity('date', true);
return date;
}
});
}
};
});
Parsing need to occur only after when input loses focus, so I use another directive, which I found here. The problem is
ctrl.$setViewValue(dateString);
won't work, because as indicated in angularjs documentation, setViewValue() must be called from within a DOM event handler. What should I do to reflect back parsing result?

Instead of
ctrl.$setViewValue(dateString);
I needed to write
elem.val(dateString);
and the problem was solved. So, my directive now looks like below:
myDirectives.directive('myDate', function ($filter) {
'use strict';
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, elem, attrs, ctrl) {
var dateFormat = attrs.myDate ? attrs.myDate : 'shortDate';
ctrl.$formatters.unshift(function (modelValue) {
return (modelValue) ? $filter('date')(modelValue, dateFormat) : '';
});
ctrl.$parsers.unshift(function (viewValue) {
var date = new Date(viewValue);
if (isNaN(date)) {
ctrl.$setValidity('date', false);
return undefined;
} else {
var dateString = $filter('date')(date, dateFormat);
if (dateString !== viewValue) {
elem.val(dateString);
}
ctrl.$setValidity('date', true);
return date;
}
});
elem.unbind('input').unbind('keydown').unbind('change');
elem.bind('blur', function () {
scope.$apply(function () {
ctrl.$setViewValue(elem.val()); //this method name is misleading;
//it actually sets model value?!
});
});
}
};
});
Note, that I incorporated the code from another directive which was responsible for pushing view value to model, when focus is lost.

I've created a date parser that converts string to Date objects. You can also provide date formats you're using, as well as your date locale. It returns a Date object, valid or otherwise depending on the input.
There's also a directive that implements this parser so you can use it on an input field, for example.
https://github.com/dnasir/angular-dateParser

This should work:
var setter = $parse('ngModel').assign;
setter($scope,valueToSet);
$scope.$apply() // This may be needed depending on where its done.
Refenece: http://docs.angularjs.org/api/ng.$parse

on html:
<input class="form-control""
type="date"
string-to-date
ng-model="val.VAL">
On Controller create a directive
.directive('stringToDate', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(value) {
return new Date(value);
});
ngModel.$formatters.push(function(value) {
var fechaactual = new Date(value);
return fechaactual;
});
}
};
})
note that the directive call stringToDate and you have to call it as string-to-date in the html. Dont not why :)

Related

Parameter transfer does not work in the Angularjs directive

The page has a calendar from Angular - md-datepicker with the following parameters:
<md-datepicker
tzoned-date
ng-model="objt.datesince"
timezone="objt.timezone"
ng-required>
</md-datepicker>
directives code:
.directive('tzonedDate', function () {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, elem, attrs, ngModel) {
var toView = function (val) {
var momentd = moment.utc(val).utcOffset(attrs.timezone);
return new Date(momentd.format('YYYY-MM-DD'));
};
var toModel = function (val) {
return moment(val);
};
ngModel.$formatters.unshift(toView);
ngModel.$parsers.unshift(toModel);
}
};
})
Actually the problem is that the time parameter in the directive is passed normally, but the time zone parameter is always passed empty (I tried write timezone = "{{objt.timezone}}") The result is the same.
Use $scope.$eval:
.directive('tzonedDate', function () {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, elem, attrs, ngModel) {
ngModel.$formatters.unshift(toView);
ngModel.$parsers.unshift(toModel);
function toView(val) {
//var momentd = moment.utc(val).utcOffset(attrs.timezone);
//Use scope.$eval
var timezone = scope.$eval(attrs.timezone);
var momentd = moment.utc(val).utcOffset(timezone);
return new Date(momentd.format('YYYY-MM-DD'));
}
function toModel(val) {
return moment(val);
}
}
};
})
Use $eval to evaluate the the timezone attribute as an Angular Expression in the context of the directive scope.

angularjs Date Input only

I am trying to validate input for date (dd/mm/yyy) and then if it is not date don't allow to enter - such as strings or anything
Below is my code but not working - means allowing users to enter anything
angular.module('app')
.directive('onlyDates', function () {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, element, attr, ctrl) {
function inputValue(val) {
if (val) {
var reg = /^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\/02\/((19|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/g;
var res = reg.test(val);
if (!res) {
ctrl.$setViewValue(val);
ctrl.$render();
return NaN;
}
return val;
}
return undefined;
}
ctrl.$parsers.push(inputValue);
}
};
});
you could try using ng-change and some function that can check "as you type" that what you are entering matches your pattern, and if not, delete the last char entered.
<input type="text" ng-model="txtValue" ng-change="TextBoxChangedCheck()">
$scope.TextBoxChangedCheck = function () {
//// enter your text validation here
};

javascript + regular expression

I have a problem. I want to use a regular expression that let me introduce just numbers and "." (for decimals) on an input field, no letters and other special character.
I'm trying this:
option 1 => var restrict="[^\d+]"
option 2 => var restrict="[^\d+]"
iAttrs.restrict = ^(?![0-9]+([.]?[0-9]+))$
value.toLowerCase().replace(new RegExp(iAttrs.restrict, 'g'), '');
This regular expression is an angular directive
appModule.directive('restrict', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, iElement, iAttrs, controller) {
scope.$watch(iAttrs.ngModel, function(value) {
if (!value) {
return;
}
value = value.toString();
$parse(iAttrs.ngModel).assign(scope, value.toLowerCase().replace(new RegExp(iAttrs.restrict, 'g'), ''));
});
}
}
});
It must remove the wrong characters written on the input. But the problem is
option 1 ==> don't let me write "." character
option 2 ==> don't let me write nothing (when I have a default value example: "300.21" that must appear on the input field ... after restrict directive finish, nothing is written on the input.
Can somebody help me?
Thanks
Updated based on comments:
As the case for decimal point is very particular, I created a new directive for that.
Directive Code:
angular.module("app",[]).directive('allowOnlyDecimal', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, iElement, iAttrs, controller) {
// used to find out if more than one . is available
function nth_ocurrence(str, needle, nth) {
for (i=0;i<str.length;i++) {
if (str.charAt(i) == needle) {
if (!--nth) {
return i;
}
}
}
return false;
}
scope.$watch(iAttrs.ngModel, function(value) {
if (!value) {
return;
}
value = value.toString();
var temp = value.replace(/[^(\d\.)]/gi, '');
// this removes any special character and alphabets
if(nth_ocurrence(temp,".",2)) {
temp = temp.substr(0, nth_ocurrence(temp,".",2));
// removes if user enters more than one decimal point
}
scope[iAttrs.ngModel] = temp;
});
}
};
}).controller("MainController", function($scope) {
$scope.someInput = 100.400;
});
In your HTML:
<input type="text" ng-model="someInput" allow-only-decimal>
WORKING DEMO
OLD ANSWER:
This could be more generic approach which you can use for most of the restrict functionality using regex.
HTML :
<input type="text" ng-model="someInput" restrict="[^(\d\.)]">
JS:
angular.module("app",[]).directive('restrict', function($parse, $timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, iElement, iAttrs, controller) {
scope.$watch(iAttrs.ngModel, function(value) {
if (!value) {
return;
}
value = value.toString();
$timeout(function() {
scope[iAttrs.ngModel] = value.replace(new RegExp(iAttrs.restrict,'gi'), '');
},10);
});
}
};
}).controller("MainController", function($scope) {
$scope.someInput = 100.400;
});
You can use a simple regex like this:
^\d+\.*\d*$
Working demo

decimal validation directive in angularjs

I wanted to create directive in angular that would display error message if entered value is not in valid format.
What I finally came with is:
http://plnkr.co/edit/l2CWu8u6sMtSj3l0kdvd?p=preview
app.directive('kbDecimalValidation', function ($parse, $rootScope, $compile) {
return {
restrict: 'E',
scope: {
inputFieldRef: '=?',
kbModel: '=ngModel',
kbRequired: '#required',
inputName: '#'
},
template: '<span ng-form="kbDecimalValidationForm">' +
'<input ng-model="kbModel" ng-required="kbRequired" ' +
'size="6"' +
'ng-pattern="/^[0-9]+(\\.[0-9][0-9]?)?$/" ' +
'/>' +
'<div ng-show="!kbDecimalValidationForm[inputName].$valid && kbDecimalValidationForm[inputName].$error.required"' +
'style="color: red; font-weight: bold">Field is required</div>' +
'<div ng-show="!kbDecimalValidationForm[inputName].$valid && kbDecimalValidationForm[inputName].$error.pattern"' +
'style="color: red; font-weight: bold">Bad format format,<br />allowed: "0.00"' +
'</div>' +
'</span>',
replace: true,
priority: 50,
controller: function($scope){
$scope.$watch(
'kbDecimalValidationForm[inputName]',
function (value) {
$scope.inputFieldRef = value;
});
},
compile: function (tElement, tAttrs, transclude) {
if($.tempKbDecimalValidationGUID == undefined){
$.tempKbDecimalValidationGUID = 0;
}
var guidInputName = 'XXX' + ++$.tempKbDecimalValidationGUID + 'XXX';
$(tElement).find('input').attr('name', guidInputName); //it is here to force angular to assign value to: $scope.kbDecimalValidationForm[guidInputName]
//there is no expression in name, so angular won't add it to $$watchers
return {
pre: function preLink($scope, iElement, iAttrs, controller) {
//$(iElement).find('input').attr('name', iAttrs.inputName); //it doesn't work if there is expression in inputName,
// expression will be evaluated later (after linkFunction)
// and the name assigned here will be updated (re-parsed by angular watch)
},
post: function postLink($scope, iElement, iAttrs, controller) {
$scope.kbDecimalValidationForm[iAttrs.inputName] = $scope.kbDecimalValidationForm[guidInputName]; //rewrite value to make it available by parsed name
$(iElement).find('input').attr('name', iAttrs.inputName); //assign parsed name - GUID didn't contain expression, so it is not in $$watchers,
// so it won't be replaced by angular
}
}
}
};
});
but I'm sure it is not propper way to do it. I expirience a lot of problems with it. Can somebody tell me what is the propper way to achieve it?
PS: The problem I'm facing right now with the above directive is: when I use it in ng-repeat, and reorder repeated source the directive does not work correctly. I suspect the problem is with my "hacking coding" (the tempKbDecimalValidationGUID, and $scope.kbDecimalValidationForm variables)
For Angular 1.2.x, you will have to use the ngModel.$parsers and $formatters pipelines for validation. Angular 1.3 has the dedicated $validators and even $asyncValidators pipelines. So the outline of a validation solution for 1.2.x would be:
.directive("kbDecimalValidation", function() {
function parseDecimal(value) {
// implement the conversion from a string to number, e.g. (simpistic):
var val = parseFloat(value);
// return a number (for success), null (for empty input), or a string (describing the error on error)
}
function formatDecimal(value) {
// format a number to a string that will be displayed; the inverse of parseDecimal()
// throw error if something goes wrong
}
return {
restrict: "A",
require: "ngModel",
link: function(scope, elem, attrs, ngModel) {
ngModel.$parsers.push(function(value) {
var val = parseDecimal(value);
if( typeof val === "string" ) {
// an error occured
ngModel.$setValidity("kbDecimal", false);
// return undefined!
}
else {
ngModel.$setValidity("kbDecimal", true);
return val;
}
});
ngModel.$formaters.push(function(value) {
if( value == null || typeof value === "number" ) {
ngModel.$setValidity("kbDecimal", true);
try {
return formatDecimal(value);
}
catch(e) {
ngModel.$setValidity("kbDecimal", false);
return "";
}
}
else {
ngModel.$setValidity("kbDecimal", false);
return "";
}
});
}
};
})
Many details will need work, but hopefully you get the idea. The parseDecimal()/formatDecimal() functions could even go to a dedicated Angular service, if they become too complex, or need to be reusable.
About the display of error messages
A quick and dirty way is to use DOM manipulation through the elem argument of link(). E.g.:
link: function(scope, elem, attrs, ngModel) {
...
scope.$watch(
function() { return ngModel.$error.kbDecimal; },
function(newval) {
var container = elem.parent();
// append or remove the message as necessary
...
}
);
}
Another way, less quick but more componentized is to make 2 more directives. One will be placed on the <span ng-form> element (the container), another will display the messages. The HTML would be like:
<span ng-form="..." validation-container>
<input ... kb-decimal-validation />
<validation-messages></validation-messages>
</span>
Both kbDecimalValidation and validationMessages will require the validationContainer; the controller of the validationContainer will have a method, called by the kbDecimalValidation, to get notified about the $error object. It will also expose a copy of the $error object. The validationMessages will $watch that object and display or hide the appropriate messages.

Why is my directive messing up my validations?

I ripped this directive off from this SO question.
var directive = function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, element, attr, ctrl) {
function inputValue(val) {
if (val) {
var digits = val.replace(/[^0-9]/g, '');
if (digits !== val) {
ctrl.$setViewValue(digits);
ctrl.$render();
}
return parseInt(digits, 10);
}
return undefined;
}
ctrl.$parsers.push(inputValue);
}
};
};
module.exports = directive;
I want to limit my input to only accept numbers, but now my other validators, like min and max, are not working. My input is now always in an error state. What part of this directive is effecting the validation lifecycle in angular?

Categories

Resources