Reputation: 2117
I need a directive for filtering a field for currency, so a user just needs to type and the decimal is implied.
Needs:
Start at the hundredths place as the user types. So they would type "4" and see "0.04", type "42" and see "0.42", type 298023 and see "2980.23"
-
The ng-currency filter does not fulfill these requirements as is. Please see behaviour in plunkers to see what I mean.
My First Plunker has `input = text' and allows negative numbers. One problem is that you cannot type a negative as the very first number. When you clear the field, it returns to '0.00' but it should completely clear.
app.directive('format', ['$filter', function ($filter) {
return {
require: 'ngModel', //there must be ng-model in the html
link: function (scope, elem, attr, ctrl) {
if (!ctrl) return;
ctrl.$parsers.unshift(function (viewValue, modelValue) {
var plainNumber = viewValue.replace(/[^-+0-9]/g,'');
var newVal = plainNumber.charAt(plainNumber.length-1);
var positive = plainNumber.charAt(0) != '-';
if(isNaN(plainNumber.charAt(plainNumber.length-1))){
plainNumber = plainNumber.substr(0,plainNumber.length-1)
}
//use angular internal 'number' filter
plainNumber = $filter('number')(plainNumber / 100, 2).replace(/,/g, '');
if(positive && newVal == '-'){
plainNumber = '-' + plainNumber;
}
else if(!positive && newVal == '+'){
plainNumber = plainNumber.substr(1);
}
plainNumber.replace('.', ',');
//update the $viewValue
ctrl.$setViewValue(plainNumber);
//reflect on the DOM element
ctrl.$render();
//return the modified value to next parser
return plainNumber;
});
}
};
}]);
My Second Plunker has input = text
and allows for negative input. Like the first plunker, it won't allow a negative as the first character, only after numbers are typed. The second is that it starts at the tenths place instead of the hundredths. (if you type '3' you should see '0.03' but here it shows '0.3')
app.directive('inputRestrictor', [function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ngModelCtrl) {
var pattern = /[^.0-9+-]/g;
function fromUser(text) {
if (!text)
return text;
var rep = /[+]/g;
var rem = /[-]/g;
rep.exec(text);
rem.exec(text);
var indexp = rep.lastIndex;
var indexm = rem.lastIndex;
text = text.replace(/[+.-]/g, '');
if (indexp > 0 || indexm > 0) {
if (indexp > indexm) text = "+" + text; // plus sign?
else text = "-" + text;
}
var transformedInput = text.replace(pattern, '');
transformedInput = transformedInput.replace(/([0-9]{1,2}$)/, ".$1")
ngModelCtrl.$setViewValue(transformedInput);
ngModelCtrl.$render();
return transformedInput;
}
ngModelCtrl.$parsers.push(fromUser);
}
};
}]);
How can I reconcile these solutions or tailor one to meet the requirements? I want to avoid extra libraries or add-ons. I have been told that the best approach would be to study the source for the currency filter, and recreate that filter with the additional requirements.I would love to do this, but I really don't have the skills for it right now. These two directives are what I have.
Upvotes: 12
Views: 9782
Reputation: 2542
Check this simple directive:
app.directive('price', [function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
attrs.$set('ngTrim', "false");
var formatter = function(str, isNum) {
str = String( Number(str || 0) / (isNum?1:100) );
str = (str=='0'?'0.0':str).split('.');
str[1] = str[1] || '0';
return str[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') + '.' + (str[1].length==1?str[1]+'0':str[1]);
}
var updateView = function(val) {
scope.$applyAsync(function () {
ngModel.$setViewValue(val || '');
ngModel.$render();
});
}
var parseNumber = function(val) {
var modelString = formatter(ngModel.$modelValue, true);
var sign = {
pos: /[+]/.test(val),
neg: /[-]/.test(val)
}
sign.has = sign.pos || sign.neg;
sign.both = sign.pos && sign.neg;
if (!val || sign.has && val.length==1 || ngModel.$modelValue && Number(val)===0) {
var newVal = (!val || ngModel.$modelValue && Number()===0?'':val);
if (ngModel.$modelValue !== newVal)
updateView(newVal);
return '';
}
else {
var valString = String(val || '');
var newSign = (sign.both && ngModel.$modelValue>=0 || !sign.both && sign.neg?'-':'');
var newVal = valString.replace(/[^0-9]/g,'');
var viewVal = newSign + formatter(angular.copy(newVal));
if (modelString !== valString)
updateView(viewVal);
return (Number(newSign + newVal) / 100) || 0;
}
}
var formatNumber = function(val) {
if (val) {
var str = String(val).split('.');
str[1] = str[1] || '0';
val = str[0] + '.' + (str[1].length==1?str[1]+'0':str[1]);
}
return parseNumber(val);
}
ngModel.$parsers.push(parseNumber);
ngModel.$formatters.push(formatNumber);
}
};
}]);
And use it like this:
<input type="text" ng-model="number" price >
See it live in this PLUNKER (July 14)
Upvotes: 8
Reputation: 23768
Angular Numeric
Angular Numeric is a sophisticated directive that implements a complete numeric input field.
Very simple to use - yet powerful.
<input numeric min="-20" max="100" decimals="3" />
There are checks on min and max values. When the value falls below the minumum the value is set to the minumum value. When the value exceeds the maxiumum, the value is set to the maximum.
Formatting is done on the blur event; thousand separator and decimal are based on the current Angular locale.
The number of decimals can be set.
https://www.npmjs.com/package/angular-numeric-directive
Upvotes: 1
Reputation: 2075
i think this can full fill your requirement
https://github.com/FCSAmerica/angular-fcsa-number
you can restrict the input that allows only numbers with decimals by default angular input validations or by using char code.
Upvotes: 3