Skip to content

Commit a27889a

Browse files
author
Marvin Tam
committed
1.0.0: Rewrite directive backed by full suite of unit tests
1 parent 1199e22 commit a27889a

7 files changed

+281
-77
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bower_components
2+
node_modules

Diff for: README.md

+17-3
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,30 @@
22

33
This directive validates monetary inputs in "42.53" format (some additional work is needed for "32,00" Europoean formats). Note that this is _not_ designed to work with currency symbols. It largely behaves like Angular's implementation of `type="number"`.
44

5-
It does just a few things:
5+
It does a few things:
66

77
- Prevents entering non-numeric characters
88
- Prevents entering the minus sign when `min >= 0`
9+
- Supports `min` and `max` like in `<input type="number">`
10+
- Rounds the model value by `precision`, e.g. `42.219` will be rounded to `42.22` by default
911
- On `blur`, the input field is auto-formatted. Say if you enter `42`, it will be formatted to `42.00`
1012

1113

1214
## Usage:
15+
16+
### Attributes:
17+
- `money`: _required_
18+
- `ng-model`: _required_
19+
- `type`: Set to `text` or just leave it out. Do _not_ set to `number`.
20+
- `min`: _optional_ Defaults to `0`.
21+
- `max`: _optional_ Not enforced by default
22+
- `precision`: _optional_ Defaults to `2`. Set to `-1` to disable rounding
23+
1324
``` html
14-
<input type="text" money min="1" max="100">
25+
<input type="text" ng-model="model.price" money>
1526
```
1627

17-
`min` defaults to `0`; set `min=-Infinity` to allow negative values.
28+
## Tests:
29+
30+
1. Install test deps: `npm install` and `bower install`
31+
1. Run: `./node_modules/karma/bin/karma start`

Diff for: angular-money-directive.js

+83-71
Original file line numberDiff line numberDiff line change
@@ -8,93 +8,105 @@ angular.module('fiestah.money', [])
88
'use strict';
99

1010
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
11-
function isUndefined(value) {
12-
return typeof value == 'undefined';
13-
}
14-
function isEmpty(value) {
15-
return isUndefined(value) || value === '' || value === null || value !== value;
16-
}
1711

18-
return {
19-
restrict: 'A',
20-
require: 'ngModel',
21-
link: function (scope, el, attr, ctrl) {
22-
function round(num) {
23-
return Math.round(num * 100) / 100;
12+
function link(scope, el, attrs, ngModelCtrl) {
13+
var min = parseFloat(attrs.min || 0);
14+
var precision = parseFloat(attrs.precision || 2);
15+
var lastValidValue;
16+
17+
function round(num) {
18+
var d = Math.pow(10, precision);
19+
return Math.round(num * d) / d;
20+
}
21+
22+
function formatPrecision(value) {
23+
return parseFloat(value).toFixed(precision);
24+
}
25+
26+
function formatViewValue(value) {
27+
return ngModelCtrl.$isEmpty(value) ? '' : '' + value;
28+
}
29+
30+
31+
ngModelCtrl.$parsers.push(function (value) {
32+
// Handle leading decimal point, like ".5"
33+
if (value.indexOf('.') === 0) {
34+
value = '0' + value;
2435
}
2536

26-
var min = parseFloat(attr.min) || 0;
37+
// Allow "-" inputs only when min < 0
38+
if (value.indexOf('-') === 0 && min >= 0) {
39+
value = null;
40+
ngModelCtrl.$setViewValue('');
41+
ngModelCtrl.$render();
42+
}
2743

28-
// Returning NaN so that the formatter won't render invalid chars
29-
ctrl.$parsers.push(function(value) {
30-
// Handle leading decimal point, like ".5"
31-
if (value === '.') {
32-
ctrl.$setValidity('number', true);
33-
return 0;
34-
}
44+
var empty = ngModelCtrl.$isEmpty(value);
45+
if (empty || NUMBER_REGEXP.test(value)) {
46+
lastValidValue = (value === '')
47+
? null
48+
: (empty ? value : parseFloat(value));
49+
} else {
50+
// Render the last valid input in the field
51+
ngModelCtrl.$setViewValue(formatViewValue(lastValidValue));
52+
ngModelCtrl.$render();
53+
}
3554

36-
// Allow "-" inputs only when min < 0
37-
if (value === '-') {
38-
ctrl.$setValidity('number', false);
39-
return (min < 0) ? -0 : NaN;
40-
}
55+
ngModelCtrl.$setValidity('number', true);
56+
return lastValidValue;
57+
});
58+
ngModelCtrl.$formatters.push(formatViewValue);
4159

42-
var empty = isEmpty(value);
43-
if (empty || NUMBER_REGEXP.test(value)) {
44-
ctrl.$setValidity('number', true);
45-
return value === '' ? null : (empty ? value : parseFloat(value));
46-
} else {
47-
ctrl.$setValidity('number', false);
48-
return NaN;
49-
}
50-
});
51-
ctrl.$formatters.push(function(value) {
52-
return isEmpty(value) ? '' : '' + value;
53-
});
60+
var minValidator = function(value) {
61+
if (!ngModelCtrl.$isEmpty(value) && value < min) {
62+
ngModelCtrl.$setValidity('min', false);
63+
return undefined;
64+
} else {
65+
ngModelCtrl.$setValidity('min', true);
66+
return value;
67+
}
68+
};
69+
ngModelCtrl.$parsers.push(minValidator);
70+
ngModelCtrl.$formatters.push(minValidator);
5471

55-
var minValidator = function(value) {
56-
if (!isEmpty(value) && value < min) {
57-
ctrl.$setValidity('min', false);
72+
if (attrs.max) {
73+
var max = parseFloat(attrs.max);
74+
var maxValidator = function(value) {
75+
if (!ngModelCtrl.$isEmpty(value) && value > max) {
76+
ngModelCtrl.$setValidity('max', false);
5877
return undefined;
5978
} else {
60-
ctrl.$setValidity('min', true);
79+
ngModelCtrl.$setValidity('max', true);
6180
return value;
6281
}
6382
};
64-
ctrl.$parsers.push(minValidator);
65-
ctrl.$formatters.push(minValidator);
66-
67-
if (attr.max) {
68-
var max = parseFloat(attr.max);
69-
var maxValidator = function(value) {
70-
if (!isEmpty(value) && value > max) {
71-
ctrl.$setValidity('max', false);
72-
return undefined;
73-
} else {
74-
ctrl.$setValidity('max', true);
75-
return value;
76-
}
77-
};
78-
79-
ctrl.$parsers.push(maxValidator);
80-
ctrl.$formatters.push(maxValidator);
81-
}
8283

83-
// Round off to 2 decimal places
84-
ctrl.$parsers.push(function (value) {
84+
ngModelCtrl.$parsers.push(maxValidator);
85+
ngModelCtrl.$formatters.push(maxValidator);
86+
}
87+
88+
// Round off
89+
if (precision > -1) {
90+
ngModelCtrl.$parsers.push(function (value) {
8591
return value ? round(value) : value;
8692
});
87-
ctrl.$formatters.push(function (value) {
88-
return value ? parseFloat(value).toFixed(2) : value;
89-
});
90-
91-
el.bind('blur', function () {
92-
var value = ctrl.$modelValue;
93-
if (value) {
94-
ctrl.$viewValue = round(value).toFixed(2);
95-
ctrl.$render();
96-
}
93+
ngModelCtrl.$formatters.push(function (value) {
94+
return value ? formatPrecision(value) : value;
9795
});
9896
}
97+
98+
el.bind('blur', function () {
99+
var value = ngModelCtrl.$modelValue;
100+
if (value) {
101+
ngModelCtrl.$viewValue = formatPrecision(value);
102+
ngModelCtrl.$render();
103+
}
104+
});
105+
}
106+
107+
return {
108+
restrict: 'A',
109+
require: 'ngModel',
110+
link: link
99111
};
100112
});

Diff for: bower.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
{
22
"name": "angular-money-directive",
3-
"version": "0.0.3",
3+
"version": "1.0.0",
44
"main": "angular-money-directive.js",
55
"ignore": [
66
"**/.*",
77
"node_modules",
88
"components"
9-
]
10-
}
9+
],
10+
"devDependencies": {
11+
"angular": "~1.2.16",
12+
"angular-mocks": "~1.2.16"
13+
}
14+
}

Diff for: karma.conf.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = function (config) {
2+
config.set({
3+
basePath: '',
4+
frameworks: ['mocha', 'chai'],
5+
files: [
6+
'bower_components/angular/angular.min.js',
7+
'bower_components/angular-mocks/angular-mocks.js',
8+
'angular-money-directive.js',
9+
'test/angular-money-directive.spec.js'
10+
],
11+
reporters: ['progress'],
12+
port: 9876,
13+
colors: true,
14+
logLevel: config.LOG_INFO,
15+
autoWatch: true,
16+
browsers: ['PhantomJS'],
17+
singleRun: false
18+
});
19+
};

Diff for: package.json

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "angular-money-directive",
3+
"version": "1.0.0",
4+
"description": "AngularJS directive to validate money inputs",
5+
"dependencies": {},
6+
"devDependencies": {
7+
"karma": "~0.12.6",
8+
"karma-mocha": "~0.1.3",
9+
"karma-phantomjs-launcher": "~0.1.4",
10+
"mocha": "~1.18.2",
11+
"chai": "~1.9.1",
12+
"karma-chai": "~0.1.0"
13+
},
14+
"scripts": {
15+
"test": "./node_modules/karma/bin/karma start --single-run"
16+
},
17+
"repository": {
18+
"type": "git",
19+
"url": "git://github.com/fiestah/angular-money-directive.git"
20+
},
21+
"author": "Marvin Tam <[email protected]>",
22+
"license": "MIT",
23+
"bugs": {
24+
"url": "https://github.com/fiestah/angular-money-directive/issues"
25+
},
26+
"homepage": "https://github.com/fiestah/angular-money-directive"
27+
}

0 commit comments

Comments
 (0)