I'm trying to create input text directive that'll only accept numbers within a specific range. I've tried parsing the value as an integer, of course min
and max
didn't work.
I do not want to use input[type="number"].
Ultimately, I'm trying to create a date of birth free input text field.
Like the one seen below:
The directive I've adapted [which i'm trying to use at the moment] - the original can be found @ angularjs: allows only numbers to be typed into a text box
app.directive('onlyDigits', function () {
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, element, attrs, modelCtrl) {
modelCtrl.$parsers.push(function (inputValue) {
if (inputValue == undefined) return '';
var transformedInput = inputValue.replace(/[^0-9]/g, '');
var theInt = parseInt(transformedInput);
if (transformedInput !== inputValue) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
return theInt;
});
}
};
What I hoped to do after I've solved this, is to do a conditional ng-show
, to show an error for a span element - when the user has typed a value over 31 (for day) 12 (for month) and so forth.
I wele any suggestions.
Thank you.
I'm trying to create input text directive that'll only accept numbers within a specific range. I've tried parsing the value as an integer, of course min
and max
didn't work.
I do not want to use input[type="number"].
Ultimately, I'm trying to create a date of birth free input text field.
Like the one seen below:
The directive I've adapted [which i'm trying to use at the moment] - the original can be found @ angularjs: allows only numbers to be typed into a text box
app.directive('onlyDigits', function () {
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, element, attrs, modelCtrl) {
modelCtrl.$parsers.push(function (inputValue) {
if (inputValue == undefined) return '';
var transformedInput = inputValue.replace(/[^0-9]/g, '');
var theInt = parseInt(transformedInput);
if (transformedInput !== inputValue) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
return theInt;
});
}
};
What I hoped to do after I've solved this, is to do a conditional ng-show
, to show an error for a span element - when the user has typed a value over 31 (for day) 12 (for month) and so forth.
I wele any suggestions.
Thank you.
Share Improve this question edited May 23, 2017 at 12:09 CommunityBot 11 silver badge asked Aug 10, 2016 at 16:22 thedapperdevthedapperdev 1311 silver badge10 bronze badges 3-
What is the reason that you don't want
type="number"
? Because of tiny scroll buttons that appear in number input? – oKonyk Commented Aug 10, 2016 at 16:28 -
Hey thanks for the message. No, I can hide those with css. Because the
minlength
andmaxlength
attrs
don't work withtype="number"
and if I did use those attrs manually then, it then requires more DOM manipulation via jQuery, so I'm trying to see if there's a more angular way of doing this with the input type staying astext
. – thedapperdev Commented Aug 10, 2016 at 16:34 - if you want to make the text filed to act as number filed i do have a solution for that but not in angular way rather in javascript way all you have to do is to check if your charCode is in range you specify. if you want i can psot the code here – Joel Joseph Commented Aug 10, 2016 at 16:45
3 Answers
Reset to default 3I had the exact same problem. I tried "everything" to make it both user friendly and to not accept invalid values. Finally I gave up on apparently easy solutions, like ng-pattern
, and with help of a friend @Teemu Turkia, we came up with integers-only
directive.
It uses type="text"
, supports both min
and max
, do not accept chars beyond numbers and -
(as a first character in case minimum is negative) to be typed.
Also, ng-model
is never assigned with invalid value such as empty string or NaN
, only values between given range or null
.
I know, at first it looks rather intimidating ;)
HTML
// note: uses underscore.js
<body>
<form name="form">
<header>DD / MM / YYYY</header>
<section>
<input type="text"
name="day"
ng-model="day"
min="1"
max="31"
integers-only>
<input type="text"
name="month"
ng-model="month"
min="1"
max="12"
integers-only>
<input type="text"
name="year"
ng-model="year"
min="1900"
max="2016"
integers-only>
</section>
<section>
<span ng-show="form.day.$invalid">Invalid day</span>
<span ng-show="form.month.$invalid">Invalid month</span>
<span ng-show="form.year.$invalid">Invalid year</span>
</section>
</form>
</body>
JavaScript
/**
* numeric input
* <input type="text" name="name" ng-model="model" min="0" max="100" integers-only>
*/
angular.module('app', [])
.directive('integersOnly', function() {
return {
restrict: 'A',
require: 'ngModel',
scope: {
min: '=',
max: '='
},
link: function(scope, element, attrs, modelCtrl) {
function isInvalid(value) {
return (value === null || typeof value === 'undefined' || !value.length);
}
function replace(value) {
if (isInvalid(value)) {
return null;
}
var newValue = [];
var chrs = value.split('');
var allowedChars = ['0','1','2','3','4','5','6','7','8','9','-'];
for (var index = 0; index < chrs.length; index++) {
if (_.contains(allowedChars, chrs[index])) {
if (index > 0 && chrs[index] === '-') {
break;
}
newValue.push(chrs[index]);
}
}
return newValue.join('') || null;
}
modelCtrl.$parsers.push(function(value) {
var originalValue = value;
value = replace(value);
if (value !== originalValue) {
modelCtrl.$setViewValue(value);
modelCtrl.$render();
}
return value && isFinite(value) ? parseInt(value) : value;
});
modelCtrl.$formatters.push(function(value) {
if (value === null || typeof value === 'undefined') {
return null;
}
return parseInt(value);
});
modelCtrl.$validators.min = function(modelValue) {
if (scope.min !== null && modelValue !== null && modelValue < scope.min) { return false; }
return true;
};
modelCtrl.$validators.max = function(modelValue) {
if (scope.max !== null && modelValue !== null && modelValue > scope.max) { return false; }
return true;
};
modelCtrl.$validators.hasOnlyChar = function(modelValue) {
if (!isInvalid(modelValue) && modelValue === '-') { return false; }
return true;
};
}
};
});
Result
Related plunker here http://plnkr.co/edit/mIiKuw
Here is solution without any custom directives. It's still input type="number"
but needed functionality is achieved.
Here is plunker
<!DOCTYPE html>
<html>
<head></head>
<body ng-app="app" ng-controller="dobController as dob">
<h3>Date of birth form</h3>
<form name="dobForm" class="form" novalidate="">
<div>
<label for="date">DD</label>
<input type="number" ng-model="dob.date" name="date" min="1" max="31" integer />
<label for="month">MM</label>
<input type="number" ng-model="dob.month" name="month" min="1" max="12" integer />
<label for="year">YYYY</label>
<input type="number" ng-model="dob.year" name="year" min="1900" max="2016" integer />
<div style="color: red;" ng-if="dobForm.$invalid">
<p ng-show="dobForm.date.$error.min || dobForm.date.$error.max">
date must be in range 1 to 31!
</p>
<p ng-show="dobForm.month.$error.min || dobForm.month.$error.max">
month must be in range 1 to 12!
</p>
<p ng-show="dobForm.year.$error.min || dobForm.year.$error.max">
year must be in range 1900 to 2016!
</p>
</div>
</div>
</form>
<script src="https://cdnjs.cloudflare./ajax/libs/angular.js/1.5.0/angular.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/angular.js/1.5.0/angular-messages.js"></script>
<script>
var app = angular.module('app', []);
app.controller('dobController', function($scope) {});
</script>
<style>
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
</style>
</body>
</html>
This solution uses the min and max attributes to limit values of the input fields. It also uses ngModelOptions to update the model value only after a defined interval. This is to allow users to type in values before the model parser acts on the input.
angular.module("app", []);
angular.module("app").directive('onlyDigits', function() {
return {
restrict: 'A',
require: '?ngModel',
scope: {
min: "@",
max: "@"
},
link: function(scope, element, attrs, modelCtrl) {
modelCtrl.$parsers.push(function(inputValue) {
if (inputValue == undefined) return '';
var transformedInput = inputValue.replace(/[^0-9]/g, '');
var theInt = parseInt(transformedInput);
var max = scope.max;
var min = scope.min;
if (theInt > max) {
theInt = max;
} else if (theInt < min) {
theInt = min;
}
modelCtrl.$setViewValue(theInt.toString());
modelCtrl.$render();
return theInt;
});
}
}
});
<script src="https://ajax.googleapis./ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis./ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<body ng-app="app">
<input type="text" ng-model="month" ng-model-options="{ debounce: 200 }" only-digits min="1" max="12">
<input type="text" ng-model="day" ng-model-options="{ debounce: 200 }" min="1" max="30" only-digits>
<input type="text" ng-model="year" ng-model-options="{ debounce: 500 }" only-digits min="1900" max="2050">
</body>