3

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:

date-of-birth.png

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 welcome any suggestions.

Thank you.

3
  • What is the reason that you don't want type="number"? Because of tiny scroll buttons that appear in number input? Commented Aug 10, 2016 at 16:28
  • Hey thanks for the message. No, I can hide those with css. Because the minlength and maxlength attrs don't work with type="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 as text. 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 Commented Aug 10, 2016 at 16:45

3 Answers 3

3

I 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

image


Related plunker here http://plnkr.co/edit/mIiKuw

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much for your help @Mikko !, you're a genius! this worked like a charm! FYI to anyone else using this, download underscore.js so that the _.contains() function works inside the directive!
2

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.com/ajax/libs/angular.js/1.5.0/angular.js"></script> <script src="https://cdnjs.cloudflare.com/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>

enter image description here

2 Comments

And that's the reason why this is so hard! I mean this is nice solution but it still accepts e.g. consecutive + and - to be typed w/o giving $error, and also . and ,. See here i.imgur.com/MsqGQ38.png
Yep, I completely agree with this point. Accepted solution is far more comprehensive.
0

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.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="https://ajax.googleapis.com/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>

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.