(function(){
angular.module("plunker", [])
.controller('MainCtrl', function($scope) {
$scope.name;
})
})();
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.4.9/angular.js" data-semver="1.4.9"></script>
<script src="app.js"></script>
<script src="max-numbers.service.js"> </script>
<script src="max-numbers.directive.js"> </script>
</head>
<body ng-controller="MainCtrl">
<style>
input.ng-invalid{
background-color: red;
}
</style>
<strong>Characters can not be inserted : </strong> <br>
max-numbers : 5 ( - decimals if declared) <br>
decimals : 2 <br>
<strong>Validation : </strong> <br>
min : 0 <br>
max : 32.89 <br>
<hr>
<form name="form">
Only accepts numbers : <br>
<input type="text" ng-model="name" name="name" max-numbers="16" decimals="3" min="0" max="999999999.999" >
<br />
<button type="submit"> Submit </button>
</form>
<hr>
{{ name }}
</body>
</html>
(function(){
angular.module("plunker")
.factory("MaxNumbersService", [function(){
/**
* Contructor
*/
function MaxInteger(val, allowedIntegerLength, allowedDecimalLength){
this.val = val;
this.pointIndex = val.indexOf(".");
this.integersLength = (this.pointIndex !== -1) ? (val.substring(0, this.pointIndex)).length : val.length;
//TODO what if finishes by a point
this.decimalsLength = (this.pointIndex !== -1) ? (val.substring(this.pointIndex + 1)).length : 0;
this.allowedIntegerLength = allowedIntegerLength;
this.allowedDecimalLength = allowedDecimalLength;
this.hasChanged = false;
}
MaxInteger.prototype.isNotANumber = function(){
return isNaN(Number(this.val));
};
MaxInteger.prototype.handleIsNaN = function(){
while(true){
if(isNaN(Number(this.val))){
this.val = this.val.substr(0, this.val.length - 1);
} else{
break;
}
}
this.hasChanged = true;
};
MaxInteger.prototype.throwDecimals = function(){
var incrementer = (this.allowedDecimalLength) ? this.allowedDecimalLength + 1 : 0;
this.val = this.val.substring(0, this.pointIndex + incrementer);
this.hasChanged = true;
};
MaxInteger.prototype.hasDecimals = function(){
return this.val.indexOf(".") !== -1;
};
MaxInteger.prototype.hasTooMuchDecimals = function(){
return this.decimalsLength > this.allowedDecimalLength;
};
MaxInteger.prototype.hasTooMuchIntegers = function(){
return this.integersLength > this.allowedIntegerLength;
};
MaxInteger.prototype.throwIntegers = function(){
var integersLength = this.allowedIntegerLength;
if(this.pointIndex !== -1){
integersLength = (this.pointIndex <= this.allowedIntegerLength) ? this.pointIndex : this.allowedIntegerLength;
this.val = this.val.substr(0, integersLength);
} else{
this.val = this.val.substr(0, integersLength);
}
this.hasChanged = true;
};
MaxInteger.prototype.didItChanged = function(){
return this.hasChanged;
};
return MaxInteger;
}]);
})();
(function(){
angular.module("plunker")
.directive("maxNumbers", ["MaxNumbersService", function(MaxNumbersService){
return {
require : "ngModel",
link : function(scope, element, attrs, modelCtrl){
//parseInt removes spaces or ennucessary chars. To allow any falsy number
var maxLength = parseInt(attrs.maxNumbers);
var allowedIntegerLength = maxLength;
var allowedDecimalLength = 0;
if("decimals" in attrs){
allowedDecimalLength = parseInt(attrs.decimals);
allowedIntegerLength = maxLength - allowedDecimalLength;
}
if("min" in attrs){
var minimumValue = parseFloat(attrs.min);
modelCtrl.$validators.min = min;
}
if("max" in attrs){
var maximumValue = parseFloat(attrs.max);
modelCtrl.$validators.max = max;
}
modelCtrl.$parsers.unshift(function(val){
var maxInteger = new MaxNumbersService(val, allowedIntegerLength, allowedDecimalLength);
if(maxInteger.isNotANumber()){
maxInteger.handleIsNaN();
}
if(maxInteger.hasDecimals()){
if(allowedDecimalLength){
if(maxInteger.hasTooMuchDecimals()){
maxInteger.throwDecimals();
}
} else{
maxInteger.throwDecimals();
}
}
if(maxInteger.hasTooMuchIntegers()){
maxInteger.throwIntegers();
}
if(maxInteger.didItChanged()){
modelCtrl.$setViewValue(maxInteger.val);
}
modelCtrl.$render();
return maxInteger.val;
});
modelCtrl.$parsers.unshift(function(view){
return String(view);
});
modelCtrl.$parsers.push(function(view){
if(!view) return view;
return Number(view);
});
if(!("simpleLink") in attrs){
modelCtrl.$formatters.unshift(function(val){
if(!val) return val;
return String(val);
});
modelCtrl.$formatters.push(function(val){
//On load, this pipeline is always called because view needs to has model
if(!val) return val;
var maxInteger = new MaxNumbersService(val, allowedIntegerLength, allowedDecimalLength);
if(maxInteger.isNotANumber()){
maxInteger.handleIsNaN();
}
if(maxInteger.hasDecimals()){
if(allowedDecimalLength){
if(maxInteger.hasTooMuchDecimals()){
maxInteger.throwDecimals();
}
} else{
maxInteger.throwDecimals();
}
}
if(maxInteger.hasTooMuchIntegers()){
maxInteger.throwIntegers();
}
if(maxInteger.didItChanged()){
modelCtrl.$setViewValue(val);
}
return maxInteger.val;
});
}
function min(model){
return model ? model >= minimumValue : true;
}
function max(model){
return model ? model <= maximumValue : true;
}
}
};
}]);
})();