├── .gitignore
├── .npmignore
├── .travis.yml
├── README.md
├── bower.json
├── examples
└── index.html
├── gulpfile.js
├── karma.conf.js
├── package.json
├── release
├── dynamic-number.js
└── dynamic-number.min.js
├── src
└── numberDirective.js
└── test
├── directiveSpec.js
└── mobileDirectiveSpec.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.sublime-project
3 | *.sublime-workspace
4 | .DS_Store
5 | bower_components
6 | npm-debug.log
7 | .idea
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.sublime-project
3 | *.sublime-workspace
4 | .DS_Store
5 | bower_components
6 | test
7 | src
8 | examples
9 | gulpfile.js
10 | karma.conf.js
11 | README.md
12 | .travis.yml
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | cache:
4 | directories:
5 | - node_modules
6 | branches:
7 | only:
8 | - master
9 | notifications:
10 | email: false
11 | node_js:
12 | - '4.0'
13 | before_install:
14 | - npm i -g npm@^2.0.0
15 | - npm install -g bower
16 | - npm install -g gulp
17 | - bower install
18 | - npm install
19 | - gulp script
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular Dynamic Number
2 | [](https://travis-ci.org/uhlryk/angular-dynamic-number)
3 | [](https://www.npmjs.com/package/angular-dynamic-number)
4 | [](https://www.npmjs.com/package/angular-dynamic-number)
5 | [](https://www.npmjs.com/package/angular-dynamic-number)
6 | [](http://bower.io/search/?q=angular-dynamic-number)
7 |
8 | Highly customizable AngularJS directive for numbers.
9 | It validates inputs in realtime (if user press not acceptable character
10 | it wont appear in input field). This directive may be configured for each input (you can set number of digits in integer part and number of digits in decimal part, you can set decimal separator, accept only positive or negative values)
11 |
12 | The Big advantage of this directive, is separation of view value and model value. You can set comma as decimal separator (default is dot) for numbers. And then in input field there will be comma as separator, but your model value will be correct float number with dot separator.
13 |
14 | It works at realtime, therefore this model value may be use in computation for other elements and they will change real time too.
15 |
16 | It works also when you set in controller your model value (with dot) and if you set separator as comma, then in input field there will be comma.
17 |
18 | There is also filter for one directional binding (ngBind). It round fraction part to fixed number of digits. Round method can be select from
19 | Math.round, Math.ceil and Math.floor. It can show comma or dot as decimal separator.
20 |
21 | Filters can reuse strategies.
22 |
23 | It even allow to dynamically change directive properties. Just set them as models.
24 |
25 | It is also React version available [React Dynamic Number](https://github.com/uhlryk/react-dynamic-number)
26 |
27 | ## Change Log:
28 |
29 | ### v2.0.0:
30 |
31 | model type is number instead of string.
32 |
33 | ## Demo:
34 | [link](http://htmlpreview.github.io/?https://github.com/uhlryk/angular-dynamic-number/blob/master/examples/index.html)
35 |
36 | ## Features:
37 | - config max numbers for integer part and decimal part.
38 | - config decimal separator (dot or comma)
39 | - config to accept positive, negative and both numbers.
40 | - model value is correct javascript number, but view value may be correct number for localities
41 | - dynamic thousand separator (by default if decimal separator is comma then thousand separator is dot)
42 | - thousand separarator: space, dot or comma
43 | - filter with comma/dot separator and congurable number of fraction digits
44 | - filter with thousand separator
45 | - keeps cursor position
46 | - custom strategies for directive and filer
47 | - allow add currency (single character)
48 | - allow to dynamically change directive properties
49 |
50 | ## Limitations:
51 | Directive is designed for input text/tel field ( **type="text"** and **type="tel"** ). Tel field type triggers numeric keyboard on mobile devices. The input field must have ngModel.
52 |
53 | ## Installation:
54 | ### npm
55 | npm install angular-dynamic-number
56 | ### bower:
57 | bower install angular-dynamic-number
58 | then reference:
59 |
60 | bower_components/angular-dynamic-number/release/dynamic-number.js
61 |
62 | or
63 |
64 | bower_components/angular-dynamic-number/release/dynamic-number.min.js
65 | ### manualy
66 | Clone repository ```https://github.com/uhlryk/angular-dynamic-number.git``` and use in your code ```release/dynamic-number.js```
67 | ## Quick start: How to use it
68 | Add the js file to your html:
69 |
70 |
71 | Or if you use browserify and install by npm:
72 |
73 | require('angular-dynamic-number');
74 | Add the module to your dependencies:
75 |
76 | angular.module('myApp', ['dynamicNumber', ...])
77 |
78 | Add the ```awnum``` attribute to input fields:
79 |
80 |
81 |
82 | ## Options
83 | Options are also input field attributes
84 |
85 | **num-int**:
86 |
87 | Set maximum numbers of digits integer part (digits before decimal separator) (default 6).
88 |
89 | **num-fract**:
90 |
91 | Set maximum numbers of digits fraction part (digits after decimal separator) (default 2).
92 |
93 | **num-sep**:
94 |
95 | Set decimal separator (dot or comma) (default '.').
96 |
97 | **num-pos**:
98 |
99 | If true then number can be positive (default 'true').
100 |
101 | **num-neg**:
102 |
103 | If true then number can be negative (default 'true').
104 |
105 | **num-round**:
106 |
107 | Define round method for fraction part when convert from model to view and for filter
108 |
109 | **num-fixed
110 |
111 | If true then there is fixed number of fraction digets - (useful when fraction part is 00 and we need to show this zeros e.g. 12,00 )
112 |
113 | **num-thousand**:
114 |
115 | If true then number has thousand separator.
116 |
117 | **num-thousand-sep**:
118 |
119 | Set thousand separator (dot or comma or space or apostrophe) (enable if num-thousand = true, by default if num-sep equal dot then thousand separator is comma).
120 | If you want to set separator as space remember that angular by default trim spaces. You can as value set "{{' '}}"
121 |
122 | **num-prepend**
123 |
124 | Allow to set single character prepend currency e.g. $1234.12. Html could have problem with show some characters. In those situations you should set currency as html entit.
125 | € = `€`
126 |
127 | **num-append**
128 |
129 | Allow to set single character append currency e.g. 1234.12€. Html could have problem with show some characters. In those situations you should set currency as html entit.
130 | € = `€`
131 |
132 | ## Dynamic properties
133 |
134 | Some of properties of directive can be a models. And change in models change properties in directive. For example you can change currency, decimal separator etc.
135 | Demo page has example of usage dynamic properties.
136 |
137 | In short separator, integer, fraction, thousand, append are models
138 |
139 |
148 |
149 | Changes of some properties reset value in input (num-int, num-fract). Others properties after changes recreate value in input.
150 |
151 | ## Custom strategies
152 |
153 | There are multiple options for configuration each input. If you don't like write each time same options, there is for you custom strategies.
154 | You can in config file create multiple sets of configs, and use them in input file
155 |
156 | To create custom strategy in config use ```dynamicNumberStrategyProvider```. It has methods:
157 |
158 | addStrategy(strategyName, {
159 | numInt: number,
160 | numFract: number,
161 | numSep: char [\.|,],
162 | numPos: boolean [true|false],
163 | numNeg: boolean [true|false],
164 | numRound: string ['round' | 'ceil' | 'floor'],
165 | numThousand: boolean [true|false]
166 | })
167 |
168 | for example, create price strategy:
169 |
170 | app.config(['dynamicNumberStrategyProvider', function(dynamicNumberStrategyProvider){
171 | dynamicNumberStrategyProvider.addStrategy('price', {
172 | numInt: 6,
173 | numFract: 2,
174 | numSep: '.',
175 | numPos: true,
176 | numNeg: true,
177 | numRound: 'round',
178 | numThousand: true
179 | });
180 | }]);
181 |
182 | and use it in input:
183 |
184 |
185 |
186 | ## Filter options
187 |
188 | {{ expression | awnum:numFrac:numSep:numRound:numFixed:numThousand:numThousandSep:numPrepend:numAppend}}
189 |
190 | **numFrac**
191 |
192 | Set maximum numbers of digits fraction part (digits after decimal separator) (default 2).
193 |
194 | **numSep**
195 |
196 | Set decimal separator (dot or comma) (default '.').
197 |
198 | **numRound**
199 |
200 | Define round method for fraction part when convert from model to view and for filter
201 |
202 | **numFixed**
203 |
204 | If true then there is fixed number of fraction digets - (useful when fraction part is 00 and we need to show this zeros e.g. 12,00 )
205 |
206 | **numThousand**
207 |
208 | If true then number has thousand separator.
209 |
210 | **numThousandSep**
211 |
212 | Set thousand separator (dot or comma or space or apostrophe).
213 |
214 | **numPrepend**
215 |
216 | Allow to set single character prepend currency e.g. $1234.12. Html could have problem with show some characters. In those situations you should set currency as html entit.
217 | € = `€`
218 |
219 | **numAppend**
220 |
221 | Allow to set single character append currency e.g. 1234.12€. Html could have problem with show some characters. In those situations you should set currency as html entit.
222 | € = `€`
223 |
224 | ## Filter with strategies
225 |
226 | {{ expression | awnum:strategy}}
227 |
228 | **strategy**
229 |
230 | This is name of strategy
231 |
232 | ## ngTrim and spaces
233 |
234 | This is angular input directive parameter. By default it has value true, which means that it automatically trim spaces and Angular Dynamic Number don't get spaces.
235 | Therefore the best result is when you set for input ng-trim=false
236 |
237 | ## Example:
238 | Negative number with max value 9999.99 and comma as separator
239 |
240 |
241 |
242 | Negative or positive number with max value 9999.99 and comma as separator and thousand separator space
243 |
244 |
245 |
246 | Filter for number with max 3 fraction number and comma separator
247 |
248 |
{{somemodel|awnum:3:',':'round'}}
249 |
250 | ## License
251 | MIT
252 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-dynamic-number",
3 | "version": "2.5.1",
4 | "homepage": "https://github.com/uhlryk/angular-dynamic-number",
5 | "authors": [
6 | "Krzysztof Sztompka "
7 | ],
8 | "description": "Highly customizable angular directive for numbers",
9 | "main": "release/dynamic-number.js",
10 | "keywords": [
11 | "angular",
12 | "directive",
13 | "number",
14 | "format",
15 | "comma",
16 | "dot",
17 | "number separator",
18 | "thousand separator",
19 | "decimal separator"
20 | ],
21 | "license": "MIT",
22 | "ignore": [
23 | "**/.*",
24 | "node_modules",
25 | "bower_components",
26 | "test",
27 | "src",
28 | "examples",
29 | ".travis.yml",
30 | ".npmignore",
31 | "karma.conf.js",
32 | "package.json",
33 | "gulpfile.js"
34 | ],
35 | "dependencies": {
36 | "angular": "~1.4.7"
37 | },
38 | "devDependencies": {
39 | "angular-mocks": "~1.4.7"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | My Angular App
5 |
6 |
7 |
8 |
9 |
10 |
41 |
42 |
43 |
122 |
173 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var uglify = require('gulp-uglify');
3 | var rename = require("gulp-rename");
4 | var karma = require('karma').Server;
5 |
6 | gulp.task("script",function(){
7 | gulp.src(["src/*.js"])
8 | .pipe(rename("dynamic-number.js"))
9 | .pipe(gulp.dest("release"))
10 | .pipe(uglify())
11 | .pipe(rename("dynamic-number.min.js"))
12 | .pipe(gulp.dest("release"))
13 | ;
14 | });
15 | gulp.task('test', ['script'],function (done) {
16 | karma.start({
17 | configFile: __dirname + '/karma.conf.js',
18 | singleRun: true
19 | }, function() {
20 | done();
21 | });
22 | });
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Thu Oct 08 2015 22:04:02 GMT+0200 (CEST)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['jasmine'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | 'bower_components/angular/angular.js',
19 | 'bower_components/angular-mocks/angular-mocks.js',
20 | 'release/dynamic-number.js',
21 | 'test/directiveSpec.js',
22 | 'test/mobileDirectiveSpec.js'
23 | ],
24 |
25 |
26 | // list of files to exclude
27 | exclude: [
28 | ],
29 |
30 |
31 | // preprocess matching files before serving them to the browser
32 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
33 | preprocessors: {
34 | },
35 |
36 |
37 | // test results reporter to use
38 | // possible values: 'dots', 'progress'
39 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
40 | reporters: ['progress'],
41 |
42 |
43 | // web server port
44 | port: 9876,
45 |
46 |
47 | // enable / disable colors in the output (reporters and logs)
48 | colors: true,
49 |
50 |
51 | // level of logging
52 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
53 | logLevel: config.LOG_INFO,
54 |
55 |
56 | // enable / disable watching file and executing tests whenever any file changes
57 | autoWatch: false,
58 |
59 |
60 | // start these browsers
61 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
62 | browsers: ['Chrome'],
63 |
64 |
65 | // Continuous Integration mode
66 | // if true, Karma captures browsers, runs the tests and exits
67 | singleRun: false
68 | })
69 | }
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-dynamic-number",
3 | "version": "2.5.1",
4 | "description": "Highly customizable angular directive for numbers",
5 | "keywords": [
6 | "angular",
7 | "directive",
8 | "number",
9 | "format",
10 | "comma",
11 | "dot",
12 | "number separator",
13 | "thousand separator",
14 | "decimal separator"
15 | ],
16 | "main": "release/dynamic-number.js",
17 | "homepage": "https://github.com/uhlryk/angular-dynamic-number",
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/uhlryk/angular-dynamic-number.git"
21 | },
22 | "author": "Krzysztof Sztompka ",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/uhlryk/angular-dynamic-number/issues"
26 | },
27 | "scripts": {
28 | "test": "gulp test",
29 | "build": "gulp script"
30 | },
31 | "devDependencies": {
32 | "gulp": "^3.9.0",
33 | "gulp-rename": "^1.2.2",
34 | "gulp-uglify": "^1.4.1",
35 | "jasmine-core": "^2.4.1",
36 | "karma": "^0.13.10",
37 | "karma-chrome-launcher": "^0.2.1",
38 | "karma-firefox-launcher": "^0.1.6",
39 | "karma-jasmine": "^0.3.6"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/release/dynamic-number.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | (function(root, factory) {
3 | "use strict";
4 |
5 | if (typeof define === 'function' && define.amd) {
6 | define(['angular'], factory);
7 |
8 | } else if (typeof module !== 'undefined' && typeof module.exports ===
9 | 'object') {
10 | module.exports = factory(require('angular'));
11 |
12 | } else {
13 | return factory(root.angular);
14 | }
15 | }(window, function(angular) {
16 | 'use strict';
17 |
18 | var wasPasted = false;
19 |
20 | function convModelToView(modelValue, viewSeparator, prepend, append) {
21 | if (modelValue === undefined || modelValue === null || modelValue ===
22 | "") {
23 | return "";
24 | }
25 | var newViewValue = '';
26 | if (viewSeparator === ',') {
27 | newViewValue = String(modelValue).replace(".", ",");
28 | } else {
29 | newViewValue = String(modelValue);
30 | }
31 | return newViewValue;
32 | }
33 |
34 | function convViewToModel(viewValue, viewSeparator, thousandSeparator) {
35 |
36 | if (viewSeparator === ',') {
37 | return String(viewValue).replace(/['\.\s]/g, "").replace(",", ".");
38 | } else if (viewSeparator === '.') {
39 | return String(viewValue).replace(/[',\s]/g, "");
40 | }
41 | }
42 |
43 | function addPrependAppend(value, prepend, append) {
44 | var newViewValue = value;
45 | if (append) {
46 | newViewValue = newViewValue + append;
47 | }
48 | if (prepend) {
49 | if (/^\-.+/.test(newViewValue)) {
50 | newViewValue = newViewValue.replace('-', '-' + prepend);
51 | } else if (/^\-/.test(newViewValue)) {
52 | newViewValue = newViewValue;
53 | } else {
54 | newViewValue = prepend + newViewValue;
55 | }
56 | }
57 | return newViewValue;
58 | }
59 |
60 | function initIntegerPart(attrs_num_int, def_num_int) {
61 | if (attrs_num_int >= 0) {
62 | var _num_int = parseInt(attrs_num_int, 10);
63 | if (isNaN(_num_int) === false && isFinite(_num_int) && _num_int >= 0) {
64 | return _num_int;
65 | }
66 | }
67 | return def_num_int;
68 | }
69 |
70 | function initFractionPart(attrs_num_fract, def_num_fract) {
71 | if (attrs_num_fract >= 0) {
72 | var _num_fract = parseInt(attrs_num_fract, 10);
73 | if (isNaN(_num_fract) === false && isFinite(_num_fract) && _num_fract >=
74 | 0) {
75 | return _num_fract;
76 | }
77 | }
78 | return def_num_fract;
79 | }
80 |
81 | function initSeparator(attrs_num_sep, def_num_sep) {
82 | if (attrs_num_sep === ',') {
83 | return ',';
84 | } else if (attrs_num_sep === '.') {
85 | return '.';
86 | }
87 | return def_num_sep;
88 | }
89 |
90 | function initIsPositive(attrs_num_pos, def_num_pos) {
91 | if (attrs_num_pos === 'false' || attrs_num_pos === false) {
92 | return false;
93 | } else if (attrs_num_pos === 'true' || attrs_num_pos === true) {
94 | return true;
95 | }
96 | return def_num_pos;
97 | }
98 |
99 | function initIsNegative(attrs_num_neg, def_num_neg) {
100 | if (attrs_num_neg === 'false' || attrs_num_neg === false) {
101 | return false;
102 | } else if (attrs_num_neg === 'true' || attrs_num_neg === true) {
103 | return true;
104 | }
105 | return def_num_neg;
106 | }
107 |
108 | function initRound(attrs_round, def_round) {
109 | if (attrs_round === 'floor') {
110 | return Math.floor;
111 | } else if (attrs_round === 'ceil') {
112 | return Math.ceil;
113 | } else if (attrs_round === 'round') {
114 | return Math.round;
115 | }
116 | return def_round;
117 | }
118 |
119 | function initIsFixed(attrs_fixed, def_fixed) {
120 | if (attrs_fixed === 'false' || attrs_fixed === false) {
121 | return false;
122 | } else if (attrs_fixed === 'true' || attrs_fixed === true) {
123 | return true;
124 | }
125 | return def_fixed;
126 | }
127 |
128 | function initIsThousand(attrs_thousand, def_thousand) {
129 | if (attrs_thousand === 'false' || attrs_thousand === false) {
130 | return false;
131 | } else if (attrs_thousand === 'true' || attrs_thousand === true) {
132 | return true;
133 | }
134 | return def_thousand;
135 | }
136 |
137 | function initThousandSeparator(attrs_thousand, fractionSeparator,
138 | def_thousand) {
139 | if (!attrs_thousand) {
140 | return def_thousand;
141 | }
142 | var regexp;
143 | if (fractionSeparator === '.') {
144 | regexp = new RegExp('^[\',\\s]$');
145 | } else {
146 | regexp = new RegExp('^[\'\\.\\s]$');
147 | }
148 | if (regexp.test(attrs_thousand)) {
149 | return attrs_thousand;
150 | } else {
151 | return def_thousand;
152 | }
153 | }
154 |
155 | function initNumAppendPrepend(attrs_num_char) {
156 | var regexp = new RegExp('[^\\d,\\.\\s\\-]{1}');
157 | if (regexp.test(attrs_num_char)) {
158 | return attrs_num_char;
159 | }
160 | return null;
161 | }
162 |
163 |
164 |
165 | function buildRegexp(integerPart, fractionPart, fractionSeparator,
166 | isPositiveNumber, isNegativeNumber) {
167 | var negativeRegex = '-?';
168 | if (isPositiveNumber === false && isNegativeNumber === true) {
169 | negativeRegex = '-';
170 | } else if (isPositiveNumber === true && isNegativeNumber === false) {
171 | negativeRegex = '';
172 | }
173 | var intRegex = '[0-9]{0,' + (integerPart) + '}';
174 | if (integerPart === 0) {
175 | intRegex = '0';
176 | }
177 | var fractRegex = '(\\' + fractionSeparator + '([0-9]){0,' +
178 | fractionPart + '})';
179 | if (fractionPart === 0) {
180 | fractRegex = '';
181 | }
182 | return new RegExp('^' + negativeRegex + intRegex + fractRegex + '?$');
183 | }
184 |
185 | function removeLeadingZero(value) {
186 | return String(value)
187 | .replace(/^(-?)(0+)$/g, "$10") //change 00000 to '0' and -00000 to '-0'
188 | .replace(/^0(\d+)/g, "$1") //change 000001 to '1'
189 | .replace(/^-0(\d+)/g, "-$1") //change -013212 to -0
190 | .replace(new RegExp('^-([\\.,\\s])', 'g'), "-0$1") //change -. to -0.
191 | .replace(new RegExp('^[\\.,\\s]', 'g'), "0$&"); //change . to 0.
192 | }
193 |
194 | function removePrependAppendChars(value, prepend, append) {
195 | var newValue = value;
196 | if (prepend) {
197 | newValue = newValue.replace(new RegExp('[\\' + prepend + ']', 'g'),
198 | '');
199 | }
200 | if (append) {
201 | newValue = newValue.replace(new RegExp('[\\' + append + ']', 'g'), '');
202 | }
203 | return newValue;
204 | }
205 |
206 | function removeThousandSeparators(value, thousandSeparator) {
207 | if (thousandSeparator === '.') {
208 | return String(value).replace(/\./g, "");
209 | } else if (thousandSeparator === ',') {
210 | return String(value).replace(/,/g, "");
211 | } else {
212 | return String(value).replace(new RegExp('[\'\\s]', 'g'), "");
213 | }
214 | }
215 |
216 | function addThousandSeparator(value, fractionSeparator, thousandSeparator) {
217 | value = String(value).split(fractionSeparator);
218 | value[0] = value[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator);
219 | return value.join(fractionSeparator);
220 | }
221 |
222 | function addFixedZeros(value, parameters) {
223 | var newValue = value;
224 | if (parameters.isFixed) {
225 | var fractionPart = newValue.split(parameters.fractionSeparator)[1];
226 | var fractionLength = (fractionPart && fractionPart.length) ?
227 | fractionPart.length : 0;
228 | if (fractionLength === 0) {
229 | newValue += parameters.fractionSeparator;
230 | }
231 | for (var i = fractionLength; i < parameters.fractionPart; i++) {
232 | newValue += "0";
233 | }
234 | }
235 | return newValue;
236 | }
237 |
238 | function changeViewValue(ngModelController, value, parameters, state,
239 | disable) {
240 |
241 | if (disable) {
242 | state.enable = false;
243 | }
244 | var valueString = String(value);
245 | var valueWithFixedZeros = addFixedZeros(valueString, parameters);
246 | var valueWithPrependAppend = addPrependAppend(valueWithFixedZeros,
247 | parameters.prepend, parameters.append);
248 | // https://github.com/angular/angular.js/issues/13068
249 | // ngModelController.$viewValue = value;
250 | var version = angular.version;
251 | if (version.major === 1 && version.minor === 2) {
252 | ngModelController.$viewValue = valueWithPrependAppend;
253 | } else {
254 | ngModelController.$setViewValue(valueWithPrependAppend);
255 | }
256 | ngModelController.$render();
257 | }
258 |
259 | function filterModelValue(
260 | value,
261 | fractionPart,
262 | fractionSeparator,
263 | roundFunction,
264 | numFixed,
265 | isThousandSeparator,
266 | thousandSeparator,
267 | prepend,
268 | append
269 | ) {
270 | if (value === '' || value === undefined || value === null) {
271 | return '';
272 | }
273 | value = Number(value);
274 | if (!isNaN(value) && isFinite(value)) {
275 | var powerOfTen = Math.pow(10, fractionPart);
276 | if (numFixed) {
277 | value = convModelToView((roundFunction(value * powerOfTen) /
278 | powerOfTen).toFixed(fractionPart), fractionSeparator, prepend,
279 | append);
280 | } else {
281 | value = convModelToView(String(roundFunction(value * powerOfTen) /
282 | powerOfTen), fractionSeparator, prepend, append);
283 | }
284 | value = addPrependAppend(value, prepend, append);
285 | if (isThousandSeparator) {
286 | value = addThousandSeparator(value, fractionSeparator,
287 | thousandSeparator);
288 | }
289 | return value;
290 | }
291 | if (numFixed) {
292 | return (0).toFixed(fractionPart);
293 | } else {
294 | return "0";
295 | }
296 | }
297 | /**
298 | * from this source:
299 | * http://stackoverflow.com/a/2897229/4138339
300 | */
301 | function getCaretPosition(oField) {
302 | var iCaretPos = 0;
303 | if (document.selection) {
304 | oField.focus();
305 | var oSel = document.selection.createRange();
306 | oSel.moveStart('character', -oField.value.length);
307 | iCaretPos = oSel.text.length;
308 | } else if (oField.selectionStart || oField.selectionStart == '0')
309 | iCaretPos = oField.selectionDirection == 'backward' ? oField.selectionStart :
310 | oField.selectionEnd;
311 | return (iCaretPos);
312 | }
313 | /**
314 | * from this source
315 | * http://stackoverflow.com/a/22574572/4138339
316 | */
317 | function setCaretPosition(elem, caretPos) {
318 | if (elem !== null) {
319 | if (elem.createTextRange) {
320 | var range = elem.createTextRange();
321 | range.move('character', caretPos);
322 | range.select();
323 | } else {
324 | if (elem.selectionStart) {
325 | elem.focus();
326 | elem.setSelectionRange(caretPos, caretPos);
327 | } else
328 | elem.focus();
329 | }
330 | }
331 | }
332 |
333 | function countThousandSeparatorToPosition(value, separator, position) {
334 | var countPosition = 0;
335 | var countDots = 0;
336 | for (var i = 0; i < value.length; i++) {
337 | if (value[i] !== separator) {
338 | countPosition++;
339 | if (countPosition >= position) break;
340 | } else {
341 | countDots++;
342 | }
343 | }
344 | return countDots;
345 | }
346 |
347 | function cutSurplusFractionPart(value, parameters) {
348 |
349 | var newValue = value;
350 | var splitedValue = newValue.split(parameters.fractionSeparator);
351 | var integerPart = splitedValue[0];
352 | var fractionPart = splitedValue[1];
353 | if (fractionPart && fractionPart.length > parameters.fractionPart) {
354 | fractionPart = fractionPart.slice(0, parameters.fractionPart);
355 | newValue = [integerPart, fractionPart].join(parameters.fractionSeparator);
356 | }
357 | return newValue;
358 | }
359 |
360 | function prepareResponse(value) {
361 | if (value === null) {
362 | return null;
363 | } else {
364 | return Number(value);
365 | }
366 | }
367 |
368 | function createPropertyObject(scope, key, value) {
369 | var properties = {
370 | awnum: scope.awnum,
371 | numInt: scope.numInt,
372 | numFract: scope.numFract,
373 | numSep: scope.numSep,
374 | numPos: scope.numPos,
375 | numNeg: scope.numNeg,
376 | numRound: scope.numRound,
377 | numThousand: scope.numThousand,
378 | numThousandSep: scope.numThousandSep,
379 | numPrepend: scope.numPrepend,
380 | numAppend: scope.numAppend,
381 | numFixed: scope.numFixed
382 | };
383 | if (key) {
384 | properties[key] = value;
385 | }
386 | return properties;
387 | }
388 |
389 | function removeDoubledDecimalSeparators(value, parameters) {
390 | return value.replace(new RegExp("[\\" + parameters.fractionSeparator +
391 | "]+", "g"), parameters.fractionSeparator);
392 | }
393 |
394 | function initAllProperties(properties, element, attrs, ngModelController,
395 | dynamicNumberStrategy) {
396 | var strategy = {};
397 | if (properties.awnum) {
398 | strategy = dynamicNumberStrategy.getStrategy(properties.awnum);
399 | }
400 | var integerPart = initIntegerPart(properties.numInt !== undefined ?
401 | properties.numInt : strategy.numInt, 6);
402 | var fractionPart = initFractionPart(properties.numFract !== undefined ?
403 | properties.numFract : strategy.numFract, 2);
404 | var fractionSeparator = initSeparator(properties.numSep !== undefined ?
405 | properties.numSep : strategy.numSep, '.');
406 | var isPositiveNumber = initIsPositive(properties.numPos !== undefined ?
407 | properties.numPos : strategy.numPos, true);
408 | var isNegativeNumber = initIsNegative(properties.numNeg !== undefined ?
409 | properties.numNeg : strategy.numNeg, true);
410 | var roundFunction = initRound(properties.numRound !== undefined ?
411 | properties.numRound : strategy.numRound, Math.round);
412 | var isThousandSeparator = initIsThousand(properties.numThousand !==
413 | undefined ? properties.numThousand : strategy.numThousand, false);
414 | var thousandSeparator = initThousandSeparator(properties.numThousandSep !==
415 | undefined ? properties.numThousandSep : strategy.numThousandSep,
416 | fractionSeparator, fractionSeparator === '.' ? ',' : '.');
417 | var prepend = initNumAppendPrepend(properties.numPrepend !== undefined ?
418 | properties.numPrepend : strategy.numPrepend);
419 | var append = initNumAppendPrepend(properties.numAppend !== undefined ?
420 | properties.numAppend : strategy.numAppend);
421 | var isFixed = initIsFixed(properties.numFixed !== undefined ?
422 | properties.numFixed : strategy.numFixed, false);
423 | if (isPositiveNumber === false && isNegativeNumber === false) {
424 | throw new Error(
425 | 'Number is set to not be positive and not be negative. Change num_pos attr or/and num_neg attr to true'
426 | );
427 | }
428 | var viewRegexTest = buildRegexp(integerPart, fractionPart,
429 | fractionSeparator, isPositiveNumber, isNegativeNumber);
430 | return {
431 | element: element,
432 | attrs: attrs,
433 | ngModelController: ngModelController,
434 | viewRegexTest: viewRegexTest,
435 | integerPart: integerPart,
436 | fractionPart: fractionPart,
437 | fractionSeparator: fractionSeparator,
438 | isPositiveNumber: isPositiveNumber,
439 | isNegativeNumber: isNegativeNumber,
440 | roundFunction: roundFunction,
441 | isThousandSeparator: isThousandSeparator,
442 | thousandSeparator: thousandSeparator,
443 | prepend: prepend,
444 | append: append,
445 | isFixed: isFixed
446 | }
447 | }
448 |
449 | function directiveParser(value, parameters, state) {
450 | var element = parameters.element;
451 | var attrs = parameters.attrs;
452 | var ngModelController = parameters.ngModelController;
453 | var viewRegexTest = parameters.viewRegexTest;
454 | var integerPart = parameters.integerPart;
455 | var fractionPart = parameters.fractionPart;
456 | var fractionSeparator = parameters.fractionSeparator;
457 | var isPositiveNumber = parameters.isPositiveNumber;
458 | var isNegativeNumber = parameters.isNegativeNumber;
459 | var roundFunction = parameters.roundFunction;
460 | var isThousandSeparator = parameters.isThousandSeparator;
461 | var thousandSeparator = parameters.thousandSeparator;
462 | var prepend = parameters.prepend;
463 | var append = parameters.append;
464 | var isFixed = parameters.isFixed;
465 |
466 | var parsedValue = String(value);
467 |
468 | if (wasPasted) {
469 | wasPasted = false;
470 |
471 | // Remove all characters which are not number-relevant
472 | var regex = new RegExp('[^' + ((isNegativeNumber) ? '-' : '') +
473 | fractionSeparator + thousandSeparator + '0-9]+', 'g');
474 | parsedValue = parsedValue.replace(regex, '');
475 |
476 | // Remove trailing separators
477 | regex = new RegExp('^[' + fractionSeparator + thousandSeparator + ']');
478 | parsedValue = parsedValue.replace(regex, '');
479 |
480 | // Replace separator if at fraction position
481 | regex = new RegExp('[' + fractionSeparator + thousandSeparator +
482 | ']([0-9]{' + fractionPart + '})$');
483 | parsedValue = parsedValue.replace(regex, fractionSeparator + '$1');
484 | }
485 | parsedValue = removePrependAppendChars(parsedValue, prepend, append);
486 | parsedValue = removeDoubledDecimalSeparators(parsedValue, parameters);
487 | if (new RegExp('^[\.,' + thousandSeparator + ']{2,}').test(parsedValue)) {
488 | changeViewValue(ngModelController, '', parameters, state);
489 | return null;
490 | }
491 | var cursorPosition = getCaretPosition(element[0]);
492 | if (prepend) {
493 | cursorPosition--;
494 | }
495 | var valBeforeCursor = parsedValue.slice(0, cursorPosition);
496 | valBeforeCursor = removeThousandSeparators(valBeforeCursor,
497 | thousandSeparator);
498 | parsedValue = removeThousandSeparators(parsedValue, thousandSeparator);
499 | valBeforeCursor = removeLeadingZero(valBeforeCursor);
500 | var beforeRemovingLeadingZero = parsedValue;
501 | parsedValue = removeLeadingZero(parsedValue);
502 | if (parsedValue === "0" + fractionSeparator &&
503 | beforeRemovingLeadingZero === fractionSeparator && isPositiveNumber) {
504 | if (fractionPart) {
505 | changeViewValue(ngModelController, '' + fractionSeparator,
506 | parameters, state, true);
507 | setCaretPosition(element[0], 2);
508 | return null;
509 | } else {
510 | changeViewValue(ngModelController, '', parameters, state);
511 | return null;
512 | }
513 | }
514 | if (parsedValue === '' && String(value).charAt(0) === '0') {
515 | changeViewValue(ngModelController, '', parameters);
516 | return null;
517 | }
518 | if (parsedValue === undefined || parsedValue === '') {
519 | changeViewValue(ngModelController, '', parameters);
520 | return null;
521 | }
522 | if (parsedValue === '-') {
523 | if (isPositiveNumber && !isNegativeNumber) {
524 | changeViewValue(ngModelController, '', parameters, state);
525 | } else {
526 | changeViewValue(ngModelController, '-', parameters, state);
527 | }
528 | return null;
529 | }
530 |
531 | parsedValue = cutSurplusFractionPart(parsedValue, parameters);
532 | /**
533 | * view value failed 'correct view format' test
534 | * therefore view value is set from last correct model value (it must be formatted - change dot to comma)
535 | */
536 | if (viewRegexTest.test(parsedValue) === false) {
537 | var modelValue = convModelToView(ngModelController.$modelValue,
538 | fractionSeparator, parameters);
539 |
540 | if (isThousandSeparator) {
541 | modelValue = addThousandSeparator(modelValue, fractionSeparator,
542 | thousandSeparator);
543 | }
544 | changeViewValue(ngModelController, modelValue, parameters, state);
545 | setCaretPosition(element[0], cursorPosition - 1);
546 | return ngModelController.$modelValue;
547 | }
548 | /**
549 | * view value success 'correct view format' test
550 | * therefore model value is set from correct view value (it must be formatter - change comma to dot)
551 | */
552 | else {
553 | var dots = 0;
554 | var currentPosition = valBeforeCursor.length;
555 | if (isThousandSeparator) {
556 | parsedValue = addThousandSeparator(parsedValue, fractionSeparator,
557 | thousandSeparator);
558 | dots = countThousandSeparatorToPosition(parsedValue,
559 | thousandSeparator, currentPosition);
560 | }
561 | if (prepend) {
562 | dots++;
563 | if (new RegExp('^(\\-\\d)$').test(parsedValue)) {
564 | dots += 2;
565 | }
566 | if (new RegExp('^(\\d)$').test(parsedValue)) {
567 | dots++;
568 | }
569 | }
570 | changeViewValue(ngModelController, parsedValue, parameters, state);
571 | setCaretPosition(element[0], currentPosition + dots);
572 | return convViewToModel(parsedValue, fractionSeparator,
573 | thousandSeparator);
574 | }
575 | }
576 |
577 | function triggerParsers(ngModelController, value) {
578 | ngModelController.$setViewValue('');
579 | ngModelController.$render();
580 | ngModelController.$setViewValue(value);
581 | ngModelController.$render();
582 | }
583 |
584 | function onPropertyWatch(ngModelController, initObject) {
585 | var value = filterModelValue(
586 | ngModelController.$modelValue,
587 | initObject.fractionPart,
588 | initObject.fractionSeparator,
589 | initObject.roundFunction,
590 | initObject.isFixed,
591 | initObject.isThousandSeparator,
592 | initObject.thousandSeparator,
593 | initObject.prepend,
594 | initObject.append
595 | );
596 | triggerParsers(ngModelController, value);
597 | }
598 |
599 | function dynamicNumberDirective(dynamicNumberStrategy) {
600 | return {
601 | restrict: 'A',
602 | require: '?ngModel',
603 | scope: {
604 | awnum: "@",
605 | numInt: "@",
606 | numFract: "@",
607 | numSep: "@",
608 | numPos: "@",
609 | numNeg: "@",
610 | numRound: "@",
611 | numThousand: "@",
612 | numThousandSep: "@",
613 | numPrepend: "@",
614 | numAppend: "@",
615 | numFixed: "@"
616 | },
617 | link: function(scope, element, attrs, ngModelController) {
618 | if (!element[0] || element[0].tagName !== 'INPUT' || (element[0].type !==
619 | 'text' && element[0].type !== 'tel')) {
620 | console.warn(
621 | 'Directive angular-dynamic-number works only for \'input\' tag with type = \'text\' or type = \'tel\''
622 | );
623 | return;
624 | }
625 | if (!ngModelController) {
626 | console.warn(
627 | 'Directive angular-dynamic-number need ngModel attribute');
628 | return;
629 | }
630 | var initObject = initAllProperties(
631 | createPropertyObject(scope),
632 | element,
633 | attrs,
634 | ngModelController,
635 | dynamicNumberStrategy
636 | );
637 |
638 | element.on('paste', function() {
639 | wasPasted = true;
640 | });
641 |
642 | scope.$watch('numInt', function(newProperty, oldProperty) {
643 | if (oldProperty === newProperty) {
644 | return;
645 | }
646 | initObject = initAllProperties(createPropertyObject(scope,
647 | 'numInt', newProperty), element, attrs,
648 | ngModelController, dynamicNumberStrategy);
649 | onPropertyWatch(ngModelController, initObject);
650 | });
651 |
652 | scope.$watch('numFract', function(newProperty, oldProperty) {
653 | if (oldProperty === newProperty) {
654 | return;
655 | }
656 | initObject = initAllProperties(createPropertyObject(scope,
657 | 'numFract', newProperty), element, attrs,
658 | ngModelController, dynamicNumberStrategy);
659 | onPropertyWatch(ngModelController, initObject);
660 | });
661 |
662 | scope.$watch('numSep', function(newProperty, oldProperty) {
663 | if (oldProperty === newProperty) {
664 | return;
665 | }
666 | initObject = initAllProperties(createPropertyObject(scope,
667 | 'numSep', newProperty), element, attrs,
668 | ngModelController, dynamicNumberStrategy);
669 | onPropertyWatch(ngModelController, initObject);
670 | });
671 |
672 | scope.$watch('numPos', function(newProperty, oldProperty) {
673 | if (oldProperty === newProperty) {
674 | return;
675 | }
676 | initObject = initAllProperties(createPropertyObject(scope,
677 | 'numPos', newProperty), element, attrs,
678 | ngModelController, dynamicNumberStrategy);
679 | onPropertyWatch(ngModelController, initObject);
680 | });
681 |
682 | scope.$watch('numNeg', function(newProperty, oldProperty) {
683 | if (oldProperty === newProperty) {
684 | return;
685 | }
686 | initObject = initAllProperties(createPropertyObject(scope,
687 | 'numNeg', newProperty), element, attrs,
688 | ngModelController, dynamicNumberStrategy);
689 | onPropertyWatch(ngModelController, initObject);
690 | });
691 |
692 | scope.$watch('numThousand', function(newProperty, oldProperty) {
693 | if (oldProperty === newProperty) {
694 | return;
695 | }
696 | initObject = initAllProperties(createPropertyObject(scope,
697 | 'numThousand', newProperty), element, attrs,
698 | ngModelController, dynamicNumberStrategy);
699 | onPropertyWatch(ngModelController, initObject);
700 | });
701 |
702 | scope.$watch('numThousandSep', function(newProperty, oldProperty) {
703 | if (oldProperty === newProperty) {
704 | return;
705 | }
706 | initObject = initAllProperties(createPropertyObject(scope,
707 | 'numThousandSep', newProperty), element, attrs,
708 | ngModelController, dynamicNumberStrategy);
709 | onPropertyWatch(ngModelController, initObject);
710 | });
711 |
712 | scope.$watch('numAppend', function(newProperty, oldProperty) {
713 | if (oldProperty === newProperty) {
714 | return;
715 | }
716 | initObject = initAllProperties(createPropertyObject(scope,
717 | 'numAppend', newProperty), element, attrs,
718 | ngModelController, dynamicNumberStrategy);
719 | onPropertyWatch(ngModelController, initObject);
720 | });
721 |
722 | scope.$watch('numPrepend', function(newProperty, oldProperty) {
723 | if (oldProperty === newProperty) {
724 | return;
725 | }
726 | initObject = initAllProperties(createPropertyObject(scope,
727 | 'numPrepend', newProperty), element, attrs,
728 | ngModelController, dynamicNumberStrategy);
729 | onPropertyWatch(ngModelController, initObject);
730 | });
731 | scope.$watch('numFixed', function(newProperty, oldProperty) {
732 | if (oldProperty === newProperty) {
733 | return;
734 | }
735 | initObject = initAllProperties(createPropertyObject(scope,
736 | 'numFixed', newProperty), element, attrs,
737 | ngModelController, dynamicNumberStrategy);
738 | onPropertyWatch(ngModelController, initObject);
739 | });
740 | var state = {
741 | enable: true,
742 | count: 0
743 | };
744 | ngModelController.$parsers.unshift(function(value) {
745 | if (state.enable) {
746 | state.count++;
747 | return prepareResponse(directiveParser(value, initObject,
748 | state));
749 | } else {
750 | state.enable = true;
751 | return value;
752 | }
753 | });
754 | /**
755 | * it is like filter,
756 | */
757 | ngModelController.$formatters.push(function(value) {
758 | return filterModelValue(
759 | value,
760 | initObject.fractionPart,
761 | initObject.fractionSeparator,
762 | initObject.roundFunction,
763 | initObject.isFixed,
764 | initObject.isThousandSeparator,
765 | initObject.thousandSeparator,
766 | initObject.prepend,
767 | initObject.append
768 | );
769 | });
770 | }
771 | };
772 | }
773 |
774 | var moduleName = 'dynamicNumber';
775 |
776 | angular.module(moduleName, [])
777 | .provider('dynamicNumberStrategy', function() {
778 | var strategies = {};
779 |
780 | this.addStrategy = addStrategy;
781 | this.getStrategy = getStrategy;
782 | this.getStrategies = getStrategies;
783 |
784 | var strategyProvider = {
785 | addStrategy: addStrategy,
786 | getStrategy: getStrategy,
787 | getStrategies: getStrategies
788 | };
789 |
790 | this.$get = function() {
791 | return strategyProvider;
792 | };
793 |
794 | function addStrategy(name, strategy) {
795 | strategies[name] = strategy;
796 | }
797 |
798 | function getStrategy(name) {
799 | return strategies[name];
800 | }
801 |
802 | function getStrategies() {
803 | return strategies;
804 | }
805 | })
806 | .filter('awnum', ['dynamicNumberStrategy', function(
807 | dynamicNumberStrategy) {
808 | return function(value, numFract, numSep, numRound, numFixed,
809 | numThousand, numThousandSep, numPrepend, numAppend) {
810 | var strategy = {};
811 | var fractionPart;
812 | if (angular.isString(numFract)) {
813 | strategy = dynamicNumberStrategy.getStrategy(numFract);
814 | numFract = strategy.numFract;
815 | }
816 | var fractionPart = initFractionPart(numFract, 2);
817 | var fractionSeparator = initSeparator(numSep !== undefined ?
818 | numSep : strategy.numSep, '.');
819 | var roundFunction = initRound(numRound !== undefined ?
820 | numRound : strategy.numRound, Math.round);
821 | var isFixed = initIsFixed(numFixed !== undefined ? numFixed :
822 | strategy.numFixed, false);
823 | var isThousandSeparator = initIsThousand(numThousand !==
824 | undefined ? numThousand : strategy.numThousand, false);
825 | var thousandSeparator = initThousandSeparator(numThousandSep !==
826 | undefined ? numThousandSep : strategy.numThousandSep,
827 | fractionSeparator, fractionSeparator === '.' ? ',' : '.');
828 | var prepend = initNumAppendPrepend(numPrepend !== undefined ?
829 | numPrepend : strategy.numPrepend);
830 | var append = initNumAppendPrepend(numAppend !== undefined ?
831 | numAppend : strategy.numAppend);
832 | var filteredValue = filterModelValue(value, fractionPart,
833 | fractionSeparator, roundFunction, isFixed,
834 | isThousandSeparator, thousandSeparator, prepend, append);
835 | if (filteredValue === '') {
836 | return '0';
837 | }
838 | return filteredValue;
839 | };
840 | }])
841 | .directive('awnum', ['dynamicNumberStrategy', dynamicNumberDirective]);
842 |
843 | return moduleName;
844 | }));
845 |
--------------------------------------------------------------------------------
/release/dynamic-number.min.js:
--------------------------------------------------------------------------------
1 | !function(n,e){"use strict";if("function"==typeof define&&define.amd)define(["angular"],e);else{if("undefined"==typeof module||"object"!=typeof module.exports)return e(n.angular);module.exports=e(require("angular"))}}(window,function(n){"use strict";function e(n,e,t,r){if(void 0===n||null===n||""===n)return"";var u="";return u=","===e?String(n).replace(".",","):String(n)}function t(n,e,t){return","===e?String(n).replace(/['\.\s]/g,"").replace(",","."):"."===e?String(n).replace(/[',\s]/g,""):void 0}function r(n,e,t){var r=n;return t&&(r+=t),e&&(r=/^\-.+/.test(r)?r.replace("-","-"+e):/^\-/.test(r)?r:e+r),r}function u(n,e){if(n>=0){var t=parseInt(n,10);if(isNaN(t)===!1&&isFinite(t)&&t>=0)return t}return e}function a(n,e){if(n>=0){var t=parseInt(n,10);if(isNaN(t)===!1&&isFinite(t)&&t>=0)return t}return e}function i(n,e){return","===n?",":"."===n?".":e}function o(n,e){return"false"!==n&&n!==!1&&("true"===n||n===!0||e)}function c(n,e){return"false"!==n&&n!==!1&&("true"===n||n===!0||e)}function d(n,e){return"floor"===n?Math.floor:"ceil"===n?Math.ceil:"round"===n?Math.round:e}function p(n,e){return"false"!==n&&n!==!1&&("true"===n||n===!0||e)}function m(n,e){return"false"!==n&&n!==!1&&("true"===n||n===!0||e)}function s(n,e,t){if(!n)return t;var r;return r="."===e?new RegExp("^[',\\s]$"):new RegExp("^['\\.\\s]$"),r.test(n)?n:t}function f(n){var e=new RegExp("[^\\d,\\.\\s\\-]{1}");return e.test(n)?n:null}function l(n,e,t,r,u){var a="-?";r===!1&&u===!0?a="-":r===!0&&u===!1&&(a="");var i="[0-9]{0,"+n+"}";0===n&&(i="0");var o="(\\"+t+"([0-9]){0,"+e+"})";return 0===e&&(o=""),new RegExp("^"+a+i+o+"?$")}function g(n){return String(n).replace(/^(-?)(0+)$/g,"$10").replace(/^0(\d+)/g,"$1").replace(/^-0(\d+)/g,"-$1").replace(new RegExp("^-([\\.,\\s])","g"),"-0$1").replace(new RegExp("^[\\.,\\s]","g"),"0$&")}function v(n,e,t){var r=n;return e&&(r=r.replace(new RegExp("[\\"+e+"]","g"),"")),t&&(r=r.replace(new RegExp("[\\"+t+"]","g"),"")),r}function S(n,e){return"."===e?String(n).replace(/\./g,""):","===e?String(n).replace(/,/g,""):String(n).replace(new RegExp("['\\s]","g"),"")}function h(n,e,t){return n=String(n).split(e),n[0]=n[0].replace(/\B(?=(\d{3})+(?!\d))/g,t),n.join(e)}function w(n,e){var t=n;if(e.isFixed){var r=t.split(e.fractionSeparator)[1],u=r&&r.length?r.length:0;0===u&&(t+=e.fractionSeparator);for(var a=u;a=t)break}else u++;return u}function R(n,e){var t=n,r=t.split(e.fractionSeparator),u=r[0],a=r[1];return a&&a.length>e.fractionPart&&(a=a.slice(0,e.fractionPart),t=[u,a].join(e.fractionSeparator)),t}function T(n){return null===n?null:Number(n)}function b(n,e,t){var r={awnum:n.awnum,numInt:n.numInt,numFract:n.numFract,numSep:n.numSep,numPos:n.numPos,numNeg:n.numNeg,numRound:n.numRound,numThousand:n.numThousand,numThousandSep:n.numThousandSep,numPrepend:n.numPrepend,numAppend:n.numAppend,numFixed:n.numFixed};return e&&(r[e]=t),r}function y(n,e){return n.replace(new RegExp("[\\"+e.fractionSeparator+"]+","g"),e.fractionSeparator)}function E(n,e,t,r,g){var v={};n.awnum&&(v=g.getStrategy(n.awnum));var S=u(void 0!==n.numInt?n.numInt:v.numInt,6),h=a(void 0!==n.numFract?n.numFract:v.numFract,2),w=i(void 0!==n.numSep?n.numSep:v.numSep,"."),x=o(void 0!==n.numPos?n.numPos:v.numPos,!0),$=c(void 0!==n.numNeg?n.numNeg:v.numNeg,!0),F=d(void 0!==n.numRound?n.numRound:v.numRound,Math.round),N=m(void 0!==n.numThousand?n.numThousand:v.numThousand,!1),P=s(void 0!==n.numThousandSep?n.numThousandSep:v.numThousandSep,w,"."===w?",":"."),R=f(void 0!==n.numPrepend?n.numPrepend:v.numPrepend),T=f(void 0!==n.numAppend?n.numAppend:v.numAppend),b=p(void 0!==n.numFixed?n.numFixed:v.numFixed,!1);if(x===!1&&$===!1)throw new Error("Number is set to not be positive and not be negative. Change num_pos attr or/and num_neg attr to true");var y=l(S,h,w,x,$);return{element:e,attrs:t,ngModelController:r,viewRegexTest:y,integerPart:S,fractionPart:h,fractionSeparator:w,isPositiveNumber:x,isNegativeNumber:$,roundFunction:F,isThousandSeparator:N,thousandSeparator:P,prepend:R,append:T,isFixed:b}}function A(n,r,u){var a=r.element,i=(r.attrs,r.ngModelController),o=r.viewRegexTest,c=(r.integerPart,r.fractionPart),d=r.fractionSeparator,p=r.isPositiveNumber,m=r.isNegativeNumber,s=(r.roundFunction,r.isThousandSeparator),f=r.thousandSeparator,l=r.prepend,w=r.append,$=(r.isFixed,String(n));if(j){j=!1;var T=new RegExp("[^"+(m?"-":"")+d+f+"0-9]+","g");$=$.replace(T,""),T=new RegExp("^["+d+f+"]"),$=$.replace(T,""),T=new RegExp("["+d+f+"]([0-9]{"+c+"})$"),$=$.replace(T,d+"$1")}if($=v($,l,w),$=y($,r),new RegExp("^[.,"+f+"]{2,}").test($))return x(i,"",r,u),null;var b=F(a[0]);l&&b--;var E=$.slice(0,b);E=S(E,f),$=S($,f),E=g(E);var A=$;if($=g($),$==="0"+d&&A===d&&p)return c?(x(i,""+d,r,u,!0),N(a[0],2),null):(x(i,"",r,u),null);if(""===$&&"0"===String(n).charAt(0))return x(i,"",r),null;if(void 0===$||""===$)return x(i,"",r),null;if("-"===$)return p&&!m?x(i,"",r,u):x(i,"-",r,u),null;if($=R($,r),o.test($)===!1){var I=e(i.$modelValue,d,r);return s&&(I=h(I,d,f)),x(i,I,r,u),N(a[0],b-1),i.$modelValue}var M=0,V=E.length;return s&&($=h($,d,f),M=P($,f,V)),l&&(M++,new RegExp("^(\\-\\d)$").test($)&&(M+=2),new RegExp("^(\\d)$").test($)&&M++),x(i,$,r,u),N(a[0],V+M),t($,d,f)}function I(n,e){n.$setViewValue(""),n.$render(),n.$setViewValue(e),n.$render()}function M(n,e){var t=$(n.$modelValue,e.fractionPart,e.fractionSeparator,e.roundFunction,e.isFixed,e.isThousandSeparator,e.thousandSeparator,e.prepend,e.append);I(n,t)}function V(n){return{restrict:"A",require:"?ngModel",scope:{awnum:"@",numInt:"@",numFract:"@",numSep:"@",numPos:"@",numNeg:"@",numRound:"@",numThousand:"@",numThousandSep:"@",numPrepend:"@",numAppend:"@",numFixed:"@"},link:function(e,t,r,u){if(!t[0]||"INPUT"!==t[0].tagName||"text"!==t[0].type&&"tel"!==t[0].type)return void console.warn("Directive angular-dynamic-number works only for 'input' tag with type = 'text' or type = 'tel'");if(!u)return void console.warn("Directive angular-dynamic-number need ngModel attribute");var a=E(b(e),t,r,u,n);t.on("paste",function(){j=!0}),e.$watch("numInt",function(i,o){o!==i&&(a=E(b(e,"numInt",i),t,r,u,n),M(u,a))}),e.$watch("numFract",function(i,o){o!==i&&(a=E(b(e,"numFract",i),t,r,u,n),M(u,a))}),e.$watch("numSep",function(i,o){o!==i&&(a=E(b(e,"numSep",i),t,r,u,n),M(u,a))}),e.$watch("numPos",function(i,o){o!==i&&(a=E(b(e,"numPos",i),t,r,u,n),M(u,a))}),e.$watch("numNeg",function(i,o){o!==i&&(a=E(b(e,"numNeg",i),t,r,u,n),M(u,a))}),e.$watch("numThousand",function(i,o){o!==i&&(a=E(b(e,"numThousand",i),t,r,u,n),M(u,a))}),e.$watch("numThousandSep",function(i,o){o!==i&&(a=E(b(e,"numThousandSep",i),t,r,u,n),M(u,a))}),e.$watch("numAppend",function(i,o){o!==i&&(a=E(b(e,"numAppend",i),t,r,u,n),M(u,a))}),e.$watch("numPrepend",function(i,o){o!==i&&(a=E(b(e,"numPrepend",i),t,r,u,n),M(u,a))}),e.$watch("numFixed",function(i,o){o!==i&&(a=E(b(e,"numFixed",i),t,r,u,n),M(u,a))});var i={enable:!0,count:0};u.$parsers.unshift(function(n){return i.enable?(i.count++,T(A(n,a,i))):(i.enable=!0,n)}),u.$formatters.push(function(n){return $(n,a.fractionPart,a.fractionSeparator,a.roundFunction,a.isFixed,a.isThousandSeparator,a.thousandSeparator,a.prepend,a.append)})}}}var j=!1,k="dynamicNumber";return n.module(k,[]).provider("dynamicNumberStrategy",function(){function n(n,e){r[n]=e}function e(n){return r[n]}function t(){return r}var r={};this.addStrategy=n,this.getStrategy=e,this.getStrategies=t;var u={addStrategy:n,getStrategy:e,getStrategies:t};this.$get=function(){return u}}).filter("awnum",["dynamicNumberStrategy",function(e){return function(t,r,u,o,c,l,g,v,S){var h,w={};n.isString(r)&&(w=e.getStrategy(r),r=w.numFract);var h=a(r,2),x=i(void 0!==u?u:w.numSep,"."),F=d(void 0!==o?o:w.numRound,Math.round),N=p(void 0!==c?c:w.numFixed,!1),P=m(void 0!==l?l:w.numThousand,!1),R=s(void 0!==g?g:w.numThousandSep,x,"."===x?",":"."),T=f(void 0!==v?v:w.numPrepend),b=f(void 0!==S?S:w.numAppend),y=$(t,h,x,F,N,P,R,T,b);return""===y?"0":y}}]).directive("awnum",["dynamicNumberStrategy",V]),k});
--------------------------------------------------------------------------------
/src/numberDirective.js:
--------------------------------------------------------------------------------
1 | /*jslint node: true */
2 | (function(root, factory) {
3 | "use strict";
4 |
5 | if (typeof define === 'function' && define.amd) {
6 | define(['angular'], factory);
7 |
8 | } else if (typeof module !== 'undefined' && typeof module.exports ===
9 | 'object') {
10 | module.exports = factory(require('angular'));
11 |
12 | } else {
13 | return factory(root.angular);
14 | }
15 | }(window, function(angular) {
16 | 'use strict';
17 |
18 | var wasPasted = false;
19 |
20 | function convModelToView(modelValue, viewSeparator, prepend, append) {
21 | if (modelValue === undefined || modelValue === null || modelValue ===
22 | "") {
23 | return "";
24 | }
25 | var newViewValue = '';
26 | if (viewSeparator === ',') {
27 | newViewValue = String(modelValue).replace(".", ",");
28 | } else {
29 | newViewValue = String(modelValue);
30 | }
31 | return newViewValue;
32 | }
33 |
34 | function convViewToModel(viewValue, viewSeparator, thousandSeparator) {
35 |
36 | if (viewSeparator === ',') {
37 | return String(viewValue).replace(/['\.\s]/g, "").replace(",", ".");
38 | } else if (viewSeparator === '.') {
39 | return String(viewValue).replace(/[',\s]/g, "");
40 | }
41 | }
42 |
43 | function addPrependAppend(value, prepend, append) {
44 | var newViewValue = value;
45 | if (append) {
46 | newViewValue = newViewValue + append;
47 | }
48 | if (prepend) {
49 | if (/^\-.+/.test(newViewValue)) {
50 | newViewValue = newViewValue.replace('-', '-' + prepend);
51 | } else if (/^\-/.test(newViewValue)) {
52 | newViewValue = newViewValue;
53 | } else {
54 | newViewValue = prepend + newViewValue;
55 | }
56 | }
57 | return newViewValue;
58 | }
59 |
60 | function initIntegerPart(attrs_num_int, def_num_int) {
61 | if (attrs_num_int >= 0) {
62 | var _num_int = parseInt(attrs_num_int, 10);
63 | if (isNaN(_num_int) === false && isFinite(_num_int) && _num_int >= 0) {
64 | return _num_int;
65 | }
66 | }
67 | return def_num_int;
68 | }
69 |
70 | function initFractionPart(attrs_num_fract, def_num_fract) {
71 | if (attrs_num_fract >= 0) {
72 | var _num_fract = parseInt(attrs_num_fract, 10);
73 | if (isNaN(_num_fract) === false && isFinite(_num_fract) && _num_fract >=
74 | 0) {
75 | return _num_fract;
76 | }
77 | }
78 | return def_num_fract;
79 | }
80 |
81 | function initSeparator(attrs_num_sep, def_num_sep) {
82 | if (attrs_num_sep === ',') {
83 | return ',';
84 | } else if (attrs_num_sep === '.') {
85 | return '.';
86 | }
87 | return def_num_sep;
88 | }
89 |
90 | function initIsPositive(attrs_num_pos, def_num_pos) {
91 | if (attrs_num_pos === 'false' || attrs_num_pos === false) {
92 | return false;
93 | } else if (attrs_num_pos === 'true' || attrs_num_pos === true) {
94 | return true;
95 | }
96 | return def_num_pos;
97 | }
98 |
99 | function initIsNegative(attrs_num_neg, def_num_neg) {
100 | if (attrs_num_neg === 'false' || attrs_num_neg === false) {
101 | return false;
102 | } else if (attrs_num_neg === 'true' || attrs_num_neg === true) {
103 | return true;
104 | }
105 | return def_num_neg;
106 | }
107 |
108 | function initRound(attrs_round, def_round) {
109 | if (attrs_round === 'floor') {
110 | return Math.floor;
111 | } else if (attrs_round === 'ceil') {
112 | return Math.ceil;
113 | } else if (attrs_round === 'round') {
114 | return Math.round;
115 | }
116 | return def_round;
117 | }
118 |
119 | function initIsFixed(attrs_fixed, def_fixed) {
120 | if (attrs_fixed === 'false' || attrs_fixed === false) {
121 | return false;
122 | } else if (attrs_fixed === 'true' || attrs_fixed === true) {
123 | return true;
124 | }
125 | return def_fixed;
126 | }
127 |
128 | function initIsThousand(attrs_thousand, def_thousand) {
129 | if (attrs_thousand === 'false' || attrs_thousand === false) {
130 | return false;
131 | } else if (attrs_thousand === 'true' || attrs_thousand === true) {
132 | return true;
133 | }
134 | return def_thousand;
135 | }
136 |
137 | function initThousandSeparator(attrs_thousand, fractionSeparator,
138 | def_thousand) {
139 | if (!attrs_thousand) {
140 | return def_thousand;
141 | }
142 | var regexp;
143 | if (fractionSeparator === '.') {
144 | regexp = new RegExp('^[\',\\s]$');
145 | } else {
146 | regexp = new RegExp('^[\'\\.\\s]$');
147 | }
148 | if (regexp.test(attrs_thousand)) {
149 | return attrs_thousand;
150 | } else {
151 | return def_thousand;
152 | }
153 | }
154 |
155 | function initNumAppendPrepend(attrs_num_char) {
156 | var regexp = new RegExp('[^\\d,\\.\\s\\-]{1}');
157 | if (regexp.test(attrs_num_char)) {
158 | return attrs_num_char;
159 | }
160 | return null;
161 | }
162 |
163 |
164 |
165 | function buildRegexp(integerPart, fractionPart, fractionSeparator,
166 | isPositiveNumber, isNegativeNumber) {
167 | var negativeRegex = '-?';
168 | if (isPositiveNumber === false && isNegativeNumber === true) {
169 | negativeRegex = '-';
170 | } else if (isPositiveNumber === true && isNegativeNumber === false) {
171 | negativeRegex = '';
172 | }
173 | var intRegex = '[0-9]{0,' + (integerPart) + '}';
174 | if (integerPart === 0) {
175 | intRegex = '0';
176 | }
177 | var fractRegex = '(\\' + fractionSeparator + '([0-9]){0,' +
178 | fractionPart + '})';
179 | if (fractionPart === 0) {
180 | fractRegex = '';
181 | }
182 | return new RegExp('^' + negativeRegex + intRegex + fractRegex + '?$');
183 | }
184 |
185 | function removeLeadingZero(value) {
186 | return String(value)
187 | .replace(/^(-?)(0+)$/g, "$10") //change 00000 to '0' and -00000 to '-0'
188 | .replace(/^0(\d+)/g, "$1") //change 000001 to '1'
189 | .replace(/^-0(\d+)/g, "-$1") //change -013212 to -0
190 | .replace(new RegExp('^-([\\.,\\s])', 'g'), "-0$1") //change -. to -0.
191 | .replace(new RegExp('^[\\.,\\s]', 'g'), "0$&"); //change . to 0.
192 | }
193 |
194 | function removePrependAppendChars(value, prepend, append) {
195 | var newValue = value;
196 | if (prepend) {
197 | newValue = newValue.replace(new RegExp('[\\' + prepend + ']', 'g'),
198 | '');
199 | }
200 | if (append) {
201 | newValue = newValue.replace(new RegExp('[\\' + append + ']', 'g'), '');
202 | }
203 | return newValue;
204 | }
205 |
206 | function removeThousandSeparators(value, thousandSeparator) {
207 | if (thousandSeparator === '.') {
208 | return String(value).replace(/\./g, "");
209 | } else if (thousandSeparator === ',') {
210 | return String(value).replace(/,/g, "");
211 | } else {
212 | return String(value).replace(new RegExp('[\'\\s]', 'g'), "");
213 | }
214 | }
215 |
216 | function addThousandSeparator(value, fractionSeparator, thousandSeparator) {
217 | value = String(value).split(fractionSeparator);
218 | value[0] = value[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator);
219 | return value.join(fractionSeparator);
220 | }
221 |
222 | function addFixedZeros(value, parameters) {
223 | var newValue = value;
224 | if (parameters.isFixed) {
225 | var fractionPart = newValue.split(parameters.fractionSeparator)[1];
226 | var fractionLength = (fractionPart && fractionPart.length) ?
227 | fractionPart.length : 0;
228 | if (fractionLength === 0) {
229 | newValue += parameters.fractionSeparator;
230 | }
231 | for (var i = fractionLength; i < parameters.fractionPart; i++) {
232 | newValue += "0";
233 | }
234 | }
235 | return newValue;
236 | }
237 |
238 | function changeViewValue(ngModelController, value, parameters, state,
239 | disable) {
240 |
241 | if (disable) {
242 | state.enable = false;
243 | }
244 | var valueString = String(value);
245 | var valueWithFixedZeros = addFixedZeros(valueString, parameters);
246 | var valueWithPrependAppend = addPrependAppend(valueWithFixedZeros,
247 | parameters.prepend, parameters.append);
248 | // https://github.com/angular/angular.js/issues/13068
249 | // ngModelController.$viewValue = value;
250 | var version = angular.version;
251 | if (version.major === 1 && version.minor === 2) {
252 | ngModelController.$viewValue = valueWithPrependAppend;
253 | } else {
254 | ngModelController.$setViewValue(valueWithPrependAppend);
255 | }
256 | ngModelController.$render();
257 | }
258 |
259 | function filterModelValue(
260 | value,
261 | fractionPart,
262 | fractionSeparator,
263 | roundFunction,
264 | numFixed,
265 | isThousandSeparator,
266 | thousandSeparator,
267 | prepend,
268 | append
269 | ) {
270 | if (value === '' || value === undefined || value === null) {
271 | return '';
272 | }
273 | value = Number(value);
274 | if (!isNaN(value) && isFinite(value)) {
275 | var powerOfTen = Math.pow(10, fractionPart);
276 | if (numFixed) {
277 | value = convModelToView((roundFunction(value * powerOfTen) /
278 | powerOfTen).toFixed(fractionPart), fractionSeparator, prepend,
279 | append);
280 | } else {
281 | value = convModelToView(String(roundFunction(value * powerOfTen) /
282 | powerOfTen), fractionSeparator, prepend, append);
283 | }
284 | value = addPrependAppend(value, prepend, append);
285 | if (isThousandSeparator) {
286 | value = addThousandSeparator(value, fractionSeparator,
287 | thousandSeparator);
288 | }
289 | return value;
290 | }
291 | if (numFixed) {
292 | return (0).toFixed(fractionPart);
293 | } else {
294 | return "0";
295 | }
296 | }
297 | /**
298 | * from this source:
299 | * http://stackoverflow.com/a/2897229/4138339
300 | */
301 | function getCaretPosition(oField) {
302 | var iCaretPos = 0;
303 | if (document.selection) {
304 | oField.focus();
305 | var oSel = document.selection.createRange();
306 | oSel.moveStart('character', -oField.value.length);
307 | iCaretPos = oSel.text.length;
308 | } else if (oField.selectionStart || oField.selectionStart == '0')
309 | iCaretPos = oField.selectionDirection == 'backward' ? oField.selectionStart :
310 | oField.selectionEnd;
311 | return (iCaretPos);
312 | }
313 | /**
314 | * from this source
315 | * http://stackoverflow.com/a/22574572/4138339
316 | */
317 | function setCaretPosition(elem, caretPos) {
318 | if (elem !== null) {
319 | if (elem.createTextRange) {
320 | var range = elem.createTextRange();
321 | range.move('character', caretPos);
322 | range.select();
323 | } else {
324 | if (elem.selectionStart) {
325 | elem.focus();
326 | elem.setSelectionRange(caretPos, caretPos);
327 | } else
328 | elem.focus();
329 | }
330 | }
331 | }
332 |
333 | function countThousandSeparatorToPosition(value, separator, position) {
334 | var countPosition = 0;
335 | var countDots = 0;
336 | for (var i = 0; i < value.length; i++) {
337 | if (value[i] !== separator) {
338 | countPosition++;
339 | if (countPosition >= position) break;
340 | } else {
341 | countDots++;
342 | }
343 | }
344 | return countDots;
345 | }
346 |
347 | function cutSurplusFractionPart(value, parameters) {
348 |
349 | var newValue = value;
350 | var splitedValue = newValue.split(parameters.fractionSeparator);
351 | var integerPart = splitedValue[0];
352 | var fractionPart = splitedValue[1];
353 | if (fractionPart && fractionPart.length > parameters.fractionPart) {
354 | fractionPart = fractionPart.slice(0, parameters.fractionPart);
355 | newValue = [integerPart, fractionPart].join(parameters.fractionSeparator);
356 | }
357 | return newValue;
358 | }
359 |
360 | function prepareResponse(value) {
361 | if (value === null) {
362 | return null;
363 | } else {
364 | return Number(value);
365 | }
366 | }
367 |
368 | function createPropertyObject(scope, key, value) {
369 | var properties = {
370 | awnum: scope.awnum,
371 | numInt: scope.numInt,
372 | numFract: scope.numFract,
373 | numSep: scope.numSep,
374 | numPos: scope.numPos,
375 | numNeg: scope.numNeg,
376 | numRound: scope.numRound,
377 | numThousand: scope.numThousand,
378 | numThousandSep: scope.numThousandSep,
379 | numPrepend: scope.numPrepend,
380 | numAppend: scope.numAppend,
381 | numFixed: scope.numFixed
382 | };
383 | if (key) {
384 | properties[key] = value;
385 | }
386 | return properties;
387 | }
388 |
389 | function removeDoubledDecimalSeparators(value, parameters) {
390 | return value.replace(new RegExp("[\\" + parameters.fractionSeparator +
391 | "]+", "g"), parameters.fractionSeparator);
392 | }
393 |
394 | function initAllProperties(properties, element, attrs, ngModelController,
395 | dynamicNumberStrategy) {
396 | var strategy = {};
397 | if (properties.awnum) {
398 | strategy = dynamicNumberStrategy.getStrategy(properties.awnum);
399 | }
400 | var integerPart = initIntegerPart(properties.numInt !== undefined ?
401 | properties.numInt : strategy.numInt, 6);
402 | var fractionPart = initFractionPart(properties.numFract !== undefined ?
403 | properties.numFract : strategy.numFract, 2);
404 | var fractionSeparator = initSeparator(properties.numSep !== undefined ?
405 | properties.numSep : strategy.numSep, '.');
406 | var isPositiveNumber = initIsPositive(properties.numPos !== undefined ?
407 | properties.numPos : strategy.numPos, true);
408 | var isNegativeNumber = initIsNegative(properties.numNeg !== undefined ?
409 | properties.numNeg : strategy.numNeg, true);
410 | var roundFunction = initRound(properties.numRound !== undefined ?
411 | properties.numRound : strategy.numRound, Math.round);
412 | var isThousandSeparator = initIsThousand(properties.numThousand !==
413 | undefined ? properties.numThousand : strategy.numThousand, false);
414 | var thousandSeparator = initThousandSeparator(properties.numThousandSep !==
415 | undefined ? properties.numThousandSep : strategy.numThousandSep,
416 | fractionSeparator, fractionSeparator === '.' ? ',' : '.');
417 | var prepend = initNumAppendPrepend(properties.numPrepend !== undefined ?
418 | properties.numPrepend : strategy.numPrepend);
419 | var append = initNumAppendPrepend(properties.numAppend !== undefined ?
420 | properties.numAppend : strategy.numAppend);
421 | var isFixed = initIsFixed(properties.numFixed !== undefined ?
422 | properties.numFixed : strategy.numFixed, false);
423 | if (isPositiveNumber === false && isNegativeNumber === false) {
424 | throw new Error(
425 | 'Number is set to not be positive and not be negative. Change num_pos attr or/and num_neg attr to true'
426 | );
427 | }
428 | var viewRegexTest = buildRegexp(integerPart, fractionPart,
429 | fractionSeparator, isPositiveNumber, isNegativeNumber);
430 | return {
431 | element: element,
432 | attrs: attrs,
433 | ngModelController: ngModelController,
434 | viewRegexTest: viewRegexTest,
435 | integerPart: integerPart,
436 | fractionPart: fractionPart,
437 | fractionSeparator: fractionSeparator,
438 | isPositiveNumber: isPositiveNumber,
439 | isNegativeNumber: isNegativeNumber,
440 | roundFunction: roundFunction,
441 | isThousandSeparator: isThousandSeparator,
442 | thousandSeparator: thousandSeparator,
443 | prepend: prepend,
444 | append: append,
445 | isFixed: isFixed
446 | }
447 | }
448 |
449 | function directiveParser(value, parameters, state) {
450 | var element = parameters.element;
451 | var attrs = parameters.attrs;
452 | var ngModelController = parameters.ngModelController;
453 | var viewRegexTest = parameters.viewRegexTest;
454 | var integerPart = parameters.integerPart;
455 | var fractionPart = parameters.fractionPart;
456 | var fractionSeparator = parameters.fractionSeparator;
457 | var isPositiveNumber = parameters.isPositiveNumber;
458 | var isNegativeNumber = parameters.isNegativeNumber;
459 | var roundFunction = parameters.roundFunction;
460 | var isThousandSeparator = parameters.isThousandSeparator;
461 | var thousandSeparator = parameters.thousandSeparator;
462 | var prepend = parameters.prepend;
463 | var append = parameters.append;
464 | var isFixed = parameters.isFixed;
465 |
466 | var parsedValue = String(value);
467 |
468 | if (wasPasted) {
469 | wasPasted = false;
470 |
471 | // Remove all characters which are not number-relevant
472 | var regex = new RegExp('[^' + ((isNegativeNumber) ? '-' : '') +
473 | fractionSeparator + thousandSeparator + '0-9]+', 'g');
474 | parsedValue = parsedValue.replace(regex, '');
475 |
476 | // Remove trailing separators
477 | regex = new RegExp('^[' + fractionSeparator + thousandSeparator + ']');
478 | parsedValue = parsedValue.replace(regex, '');
479 |
480 | // Replace separator if at fraction position
481 | regex = new RegExp('[' + fractionSeparator + thousandSeparator +
482 | ']([0-9]{' + fractionPart + '})$');
483 | parsedValue = parsedValue.replace(regex, fractionSeparator + '$1');
484 | }
485 | parsedValue = removePrependAppendChars(parsedValue, prepend, append);
486 | parsedValue = removeDoubledDecimalSeparators(parsedValue, parameters);
487 | if (new RegExp('^[\.,' + thousandSeparator + ']{2,}').test(parsedValue)) {
488 | changeViewValue(ngModelController, '', parameters, state);
489 | return null;
490 | }
491 | var cursorPosition = getCaretPosition(element[0]);
492 | if (prepend) {
493 | cursorPosition--;
494 | }
495 | var valBeforeCursor = parsedValue.slice(0, cursorPosition);
496 | valBeforeCursor = removeThousandSeparators(valBeforeCursor,
497 | thousandSeparator);
498 | parsedValue = removeThousandSeparators(parsedValue, thousandSeparator);
499 | valBeforeCursor = removeLeadingZero(valBeforeCursor);
500 | var beforeRemovingLeadingZero = parsedValue;
501 | parsedValue = removeLeadingZero(parsedValue);
502 | if (parsedValue === "0" + fractionSeparator &&
503 | beforeRemovingLeadingZero === fractionSeparator && isPositiveNumber) {
504 | if (fractionPart) {
505 | changeViewValue(ngModelController, '' + fractionSeparator,
506 | parameters, state, true);
507 | setCaretPosition(element[0], 2);
508 | return null;
509 | } else {
510 | changeViewValue(ngModelController, '', parameters, state);
511 | return null;
512 | }
513 | }
514 | if (parsedValue === '' && String(value).charAt(0) === '0') {
515 | changeViewValue(ngModelController, '', parameters);
516 | return null;
517 | }
518 | if (parsedValue === undefined || parsedValue === '') {
519 | changeViewValue(ngModelController, '', parameters);
520 | return null;
521 | }
522 | if (parsedValue === '-') {
523 | if (isPositiveNumber && !isNegativeNumber) {
524 | changeViewValue(ngModelController, '', parameters, state);
525 | } else {
526 | changeViewValue(ngModelController, '-', parameters, state);
527 | }
528 | return null;
529 | }
530 |
531 | parsedValue = cutSurplusFractionPart(parsedValue, parameters);
532 | /**
533 | * view value failed 'correct view format' test
534 | * therefore view value is set from last correct model value (it must be formatted - change dot to comma)
535 | */
536 | if (viewRegexTest.test(parsedValue) === false) {
537 | var modelValue = convModelToView(ngModelController.$modelValue,
538 | fractionSeparator, parameters);
539 |
540 | if (isThousandSeparator) {
541 | modelValue = addThousandSeparator(modelValue, fractionSeparator,
542 | thousandSeparator);
543 | }
544 | changeViewValue(ngModelController, modelValue, parameters, state);
545 | setCaretPosition(element[0], cursorPosition - 1);
546 | return ngModelController.$modelValue;
547 | }
548 | /**
549 | * view value success 'correct view format' test
550 | * therefore model value is set from correct view value (it must be formatter - change comma to dot)
551 | */
552 | else {
553 | var dots = 0;
554 | var currentPosition = valBeforeCursor.length;
555 | if (isThousandSeparator) {
556 | parsedValue = addThousandSeparator(parsedValue, fractionSeparator,
557 | thousandSeparator);
558 | dots = countThousandSeparatorToPosition(parsedValue,
559 | thousandSeparator, currentPosition);
560 | }
561 | if (prepend) {
562 | dots++;
563 | if (new RegExp('^(\\-\\d)$').test(parsedValue)) {
564 | dots += 2;
565 | }
566 | if (new RegExp('^(\\d)$').test(parsedValue)) {
567 | dots++;
568 | }
569 | }
570 | changeViewValue(ngModelController, parsedValue, parameters, state);
571 | setCaretPosition(element[0], currentPosition + dots);
572 | return convViewToModel(parsedValue, fractionSeparator,
573 | thousandSeparator);
574 | }
575 | }
576 |
577 | function triggerParsers(ngModelController, value) {
578 | ngModelController.$setViewValue('');
579 | ngModelController.$render();
580 | ngModelController.$setViewValue(value);
581 | ngModelController.$render();
582 | }
583 |
584 | function onPropertyWatch(ngModelController, initObject) {
585 | var value = filterModelValue(
586 | ngModelController.$modelValue,
587 | initObject.fractionPart,
588 | initObject.fractionSeparator,
589 | initObject.roundFunction,
590 | initObject.isFixed,
591 | initObject.isThousandSeparator,
592 | initObject.thousandSeparator,
593 | initObject.prepend,
594 | initObject.append
595 | );
596 | triggerParsers(ngModelController, value);
597 | }
598 |
599 | function dynamicNumberDirective(dynamicNumberStrategy) {
600 | return {
601 | restrict: 'A',
602 | require: '?ngModel',
603 | scope: {
604 | awnum: "@",
605 | numInt: "@",
606 | numFract: "@",
607 | numSep: "@",
608 | numPos: "@",
609 | numNeg: "@",
610 | numRound: "@",
611 | numThousand: "@",
612 | numThousandSep: "@",
613 | numPrepend: "@",
614 | numAppend: "@",
615 | numFixed: "@"
616 | },
617 | link: function(scope, element, attrs, ngModelController) {
618 | if (!element[0] || element[0].tagName !== 'INPUT' || (element[0].type !==
619 | 'text' && element[0].type !== 'tel')) {
620 | console.warn(
621 | 'Directive angular-dynamic-number works only for \'input\' tag with type = \'text\' or type = \'tel\''
622 | );
623 | return;
624 | }
625 | if (!ngModelController) {
626 | console.warn(
627 | 'Directive angular-dynamic-number need ngModel attribute');
628 | return;
629 | }
630 | var initObject = initAllProperties(
631 | createPropertyObject(scope),
632 | element,
633 | attrs,
634 | ngModelController,
635 | dynamicNumberStrategy
636 | );
637 |
638 | element.on('paste', function() {
639 | wasPasted = true;
640 | });
641 |
642 | scope.$watch('numInt', function(newProperty, oldProperty) {
643 | if (oldProperty === newProperty) {
644 | return;
645 | }
646 | initObject = initAllProperties(createPropertyObject(scope,
647 | 'numInt', newProperty), element, attrs,
648 | ngModelController, dynamicNumberStrategy);
649 | onPropertyWatch(ngModelController, initObject);
650 | });
651 |
652 | scope.$watch('numFract', function(newProperty, oldProperty) {
653 | if (oldProperty === newProperty) {
654 | return;
655 | }
656 | initObject = initAllProperties(createPropertyObject(scope,
657 | 'numFract', newProperty), element, attrs,
658 | ngModelController, dynamicNumberStrategy);
659 | onPropertyWatch(ngModelController, initObject);
660 | });
661 |
662 | scope.$watch('numSep', function(newProperty, oldProperty) {
663 | if (oldProperty === newProperty) {
664 | return;
665 | }
666 | initObject = initAllProperties(createPropertyObject(scope,
667 | 'numSep', newProperty), element, attrs,
668 | ngModelController, dynamicNumberStrategy);
669 | onPropertyWatch(ngModelController, initObject);
670 | });
671 |
672 | scope.$watch('numPos', function(newProperty, oldProperty) {
673 | if (oldProperty === newProperty) {
674 | return;
675 | }
676 | initObject = initAllProperties(createPropertyObject(scope,
677 | 'numPos', newProperty), element, attrs,
678 | ngModelController, dynamicNumberStrategy);
679 | onPropertyWatch(ngModelController, initObject);
680 | });
681 |
682 | scope.$watch('numNeg', function(newProperty, oldProperty) {
683 | if (oldProperty === newProperty) {
684 | return;
685 | }
686 | initObject = initAllProperties(createPropertyObject(scope,
687 | 'numNeg', newProperty), element, attrs,
688 | ngModelController, dynamicNumberStrategy);
689 | onPropertyWatch(ngModelController, initObject);
690 | });
691 |
692 | scope.$watch('numThousand', function(newProperty, oldProperty) {
693 | if (oldProperty === newProperty) {
694 | return;
695 | }
696 | initObject = initAllProperties(createPropertyObject(scope,
697 | 'numThousand', newProperty), element, attrs,
698 | ngModelController, dynamicNumberStrategy);
699 | onPropertyWatch(ngModelController, initObject);
700 | });
701 |
702 | scope.$watch('numThousandSep', function(newProperty, oldProperty) {
703 | if (oldProperty === newProperty) {
704 | return;
705 | }
706 | initObject = initAllProperties(createPropertyObject(scope,
707 | 'numThousandSep', newProperty), element, attrs,
708 | ngModelController, dynamicNumberStrategy);
709 | onPropertyWatch(ngModelController, initObject);
710 | });
711 |
712 | scope.$watch('numAppend', function(newProperty, oldProperty) {
713 | if (oldProperty === newProperty) {
714 | return;
715 | }
716 | initObject = initAllProperties(createPropertyObject(scope,
717 | 'numAppend', newProperty), element, attrs,
718 | ngModelController, dynamicNumberStrategy);
719 | onPropertyWatch(ngModelController, initObject);
720 | });
721 |
722 | scope.$watch('numPrepend', function(newProperty, oldProperty) {
723 | if (oldProperty === newProperty) {
724 | return;
725 | }
726 | initObject = initAllProperties(createPropertyObject(scope,
727 | 'numPrepend', newProperty), element, attrs,
728 | ngModelController, dynamicNumberStrategy);
729 | onPropertyWatch(ngModelController, initObject);
730 | });
731 | scope.$watch('numFixed', function(newProperty, oldProperty) {
732 | if (oldProperty === newProperty) {
733 | return;
734 | }
735 | initObject = initAllProperties(createPropertyObject(scope,
736 | 'numFixed', newProperty), element, attrs,
737 | ngModelController, dynamicNumberStrategy);
738 | onPropertyWatch(ngModelController, initObject);
739 | });
740 | var state = {
741 | enable: true,
742 | count: 0
743 | };
744 | ngModelController.$parsers.unshift(function(value) {
745 | if (state.enable) {
746 | state.count++;
747 | return prepareResponse(directiveParser(value, initObject,
748 | state));
749 | } else {
750 | state.enable = true;
751 | return value;
752 | }
753 | });
754 | /**
755 | * it is like filter,
756 | */
757 | ngModelController.$formatters.push(function(value) {
758 | return filterModelValue(
759 | value,
760 | initObject.fractionPart,
761 | initObject.fractionSeparator,
762 | initObject.roundFunction,
763 | initObject.isFixed,
764 | initObject.isThousandSeparator,
765 | initObject.thousandSeparator,
766 | initObject.prepend,
767 | initObject.append
768 | );
769 | });
770 | }
771 | };
772 | }
773 |
774 | var moduleName = 'dynamicNumber';
775 |
776 | angular.module(moduleName, [])
777 | .provider('dynamicNumberStrategy', function() {
778 | var strategies = {};
779 |
780 | this.addStrategy = addStrategy;
781 | this.getStrategy = getStrategy;
782 | this.getStrategies = getStrategies;
783 |
784 | var strategyProvider = {
785 | addStrategy: addStrategy,
786 | getStrategy: getStrategy,
787 | getStrategies: getStrategies
788 | };
789 |
790 | this.$get = function() {
791 | return strategyProvider;
792 | };
793 |
794 | function addStrategy(name, strategy) {
795 | strategies[name] = strategy;
796 | }
797 |
798 | function getStrategy(name) {
799 | return strategies[name];
800 | }
801 |
802 | function getStrategies() {
803 | return strategies;
804 | }
805 | })
806 | .filter('awnum', ['dynamicNumberStrategy', function(
807 | dynamicNumberStrategy) {
808 | return function(value, numFract, numSep, numRound, numFixed,
809 | numThousand, numThousandSep, numPrepend, numAppend) {
810 | var strategy = {};
811 | var fractionPart;
812 | if (angular.isString(numFract)) {
813 | strategy = dynamicNumberStrategy.getStrategy(numFract);
814 | numFract = strategy.numFract;
815 | }
816 | var fractionPart = initFractionPart(numFract, 2);
817 | var fractionSeparator = initSeparator(numSep !== undefined ?
818 | numSep : strategy.numSep, '.');
819 | var roundFunction = initRound(numRound !== undefined ?
820 | numRound : strategy.numRound, Math.round);
821 | var isFixed = initIsFixed(numFixed !== undefined ? numFixed :
822 | strategy.numFixed, false);
823 | var isThousandSeparator = initIsThousand(numThousand !==
824 | undefined ? numThousand : strategy.numThousand, false);
825 | var thousandSeparator = initThousandSeparator(numThousandSep !==
826 | undefined ? numThousandSep : strategy.numThousandSep,
827 | fractionSeparator, fractionSeparator === '.' ? ',' : '.');
828 | var prepend = initNumAppendPrepend(numPrepend !== undefined ?
829 | numPrepend : strategy.numPrepend);
830 | var append = initNumAppendPrepend(numAppend !== undefined ?
831 | numAppend : strategy.numAppend);
832 | var filteredValue = filterModelValue(value, fractionPart,
833 | fractionSeparator, roundFunction, isFixed,
834 | isThousandSeparator, thousandSeparator, prepend, append);
835 | if (filteredValue === '') {
836 | return '0';
837 | }
838 | return filteredValue;
839 | };
840 | }])
841 | .directive('awnum', ['dynamicNumberStrategy', dynamicNumberDirective]);
842 |
843 | return moduleName;
844 | }));
845 |
--------------------------------------------------------------------------------
/test/directiveSpec.js:
--------------------------------------------------------------------------------
1 | describe('Angular-dynamic-number basic', function() {
2 | beforeEach(function(){
3 | module('dynamicNumber');
4 | });
5 | describe('directive', function(){
6 | var $compile, $scope, dynamicNumberStrategyProvider;
7 | beforeEach(function(){
8 | module(function(_dynamicNumberStrategyProvider_){
9 | dynamicNumberStrategyProvider = _dynamicNumberStrategyProvider_;
10 | });
11 | });
12 | beforeEach(function () {
13 | inject(function(_$compile_, _$rootScope_){
14 | $compile = _$compile_;
15 | $scope = _$rootScope_.$new();
16 | $scope.testInput = "";
17 | });
18 | });
19 | describe('test init values', function() {
20 | it('should have view value "" and model value "" when init value is ""', function () {
21 | $scope.testInput = "";
22 | $compile('')($scope);
23 | $scope.$digest();
24 |
25 | expect($scope.testInput).toEqual('');
26 | expect($scope.testForm.testInput.$viewValue).toEqual('');
27 | });
28 | it('should have view value "12" and model value 12 when init value is 12', function () {
29 | $scope.testInput = 0;
30 | $compile('')($scope);
31 | $scope.$digest();
32 |
33 | expect($scope.testInput).toEqual(0);
34 | expect($scope.testForm.testInput.$viewValue).toEqual('0');
35 | });
36 | it('should have view value "12" and model value 12 when init value is 12', function () {
37 | $scope.testInput = 12;
38 | $compile('')($scope);
39 | $scope.$digest();
40 |
41 | expect($scope.testInput).toEqual(12);
42 | expect($scope.testForm.testInput.$viewValue).toEqual('12');
43 | });
44 | it('should have view value "" and model value null when init value is null', function () {
45 | $scope.testInput = null;
46 | $compile('')($scope);
47 | $scope.$digest();
48 |
49 | expect($scope.testInput).toEqual(null);
50 | expect($scope.testForm.testInput.$viewValue).toEqual('');
51 | });
52 | it('should have view value "" and model value undefined when init value is undefined', function () {
53 | $scope.testInput = undefined;
54 | $compile('')($scope);
55 | $scope.$digest();
56 |
57 | expect($scope.testInput).toEqual(undefined);
58 | expect($scope.testForm.testInput.$viewValue).toEqual('');
59 | });
60 | //directive modify model only when user types to input. Id doesn't change it only because init model value is wrong -this is responsibility of dev to have correct init value.
61 | it('should have view value "" and model value string when init value is string', function () {
62 | $scope.testInput = 'dsads';
63 | $compile('')($scope);
64 | $scope.$digest();
65 |
66 | expect($scope.testInput).toEqual('dsads');
67 | expect($scope.testForm.testInput.$viewValue).toEqual('0');
68 | });
69 | });
70 | describe('number format: 2 integers, decimals dot separator, positive and negative', function() {
71 | beforeEach(function(){
72 | $compile('')($scope);
73 | $scope.$digest();
74 | });
75 | it('should have view value 11.11 and model value 11.11 when set 11.11', function () {
76 | $scope.testForm.testInput.$setViewValue('11.11');
77 | expect($scope.testInput).toEqual(11.11);
78 | expect($scope.testForm.testInput.$viewValue).toEqual('11.11');
79 | });
80 | it('should have view value 0 and model value 0 when set 111.11', function () {
81 | $scope.testForm.testInput.$setViewValue('111.11');
82 | expect($scope.testInput).toEqual(null);
83 | expect($scope.testForm.testInput.$viewValue).toEqual('');
84 | });
85 | it('should have view value 0 and model value 0 when set 11.111', function () {
86 | $scope.testForm.testInput.$setViewValue('111.11');
87 | expect($scope.testInput).toEqual(null);
88 | expect($scope.testForm.testInput.$viewValue).toEqual('');
89 | });
90 | it('should have view value 0.11 and model value 0.11 when set 0.11', function () {
91 | $scope.testForm.testInput.$setViewValue('0.11');
92 | expect($scope.testInput).toEqual(0.11);
93 | expect($scope.testForm.testInput.$viewValue).toEqual('0.11');
94 | });
95 | it('should have view value 0 and model value 0 when set 00.11', function () {
96 | $scope.testForm.testInput.$setViewValue('00.11');
97 | expect($scope.testInput).toEqual(0.11);
98 | expect($scope.testForm.testInput.$viewValue).toEqual('0.11');
99 | });
100 | it('should have view value -11.11 and model value -11.11 when set -11.11', function () {
101 | $scope.testForm.testInput.$setViewValue('-11.11');
102 | expect($scope.testInput).toEqual(-11.11);
103 | expect($scope.testForm.testInput.$viewValue).toEqual('-11.11');
104 | });
105 | it('should have view value 0 and model value 0 when set -111.11', function () {
106 | $scope.testForm.testInput.$setViewValue('-111.11');
107 | expect($scope.testInput).toEqual(null);
108 | expect($scope.testForm.testInput.$viewValue).toEqual('');
109 | });
110 | it('should have view value 0 and model value 0 when set -11.111', function () {
111 | $scope.testForm.testInput.$setViewValue('-111.11');
112 | expect($scope.testInput).toEqual(null);
113 | expect($scope.testForm.testInput.$viewValue).toEqual('');
114 | });
115 | it('should have view value -0.11 and model value -0.11 when set -0.11', function () {
116 | $scope.testForm.testInput.$setViewValue('-0.11');
117 | expect($scope.testInput).toEqual(-0.11);
118 | expect($scope.testForm.testInput.$viewValue).toEqual('-0.11');
119 | });
120 | it('should have view value -0.11 and model value -0.11 when set -00.11', function () {
121 | $scope.testForm.testInput.$setViewValue('-00.11');
122 | expect($scope.testInput).toEqual(-0.11);
123 | expect($scope.testForm.testInput.$viewValue).toEqual('-0.11');
124 | });
125 | it('should have view value 0 and model value 0 when set 11,11', function () {
126 | $scope.testForm.testInput.$setViewValue('11,11');
127 | expect($scope.testInput).toEqual(null);
128 | expect($scope.testForm.testInput.$viewValue).toEqual('');
129 | });
130 | });
131 | describe('number format: 2 integers, decimals comma separator, positive and negative', function() {
132 | beforeEach(function(){
133 | var el = $compile('')($scope);
134 | $scope.$digest();
135 | });
136 | it('should have view value 11,11 and model value 11.11 when set 11,11', function () {
137 | $scope.testForm.testInput.$setViewValue('11,11');
138 | expect($scope.testInput).toEqual(11.11);
139 | expect($scope.testForm.testInput.$viewValue).toEqual('11,11');
140 | });
141 | it('should have view value 0,11 and model value 0.11 when set 0,11', function () {
142 | $scope.testForm.testInput.$setViewValue('0,11');
143 | expect($scope.testInput).toEqual(0.11);
144 | expect($scope.testForm.testInput.$viewValue).toEqual('0,11');
145 | });
146 | it('should have view value -11,11 and model value -11.11 when set -11,11', function () {
147 | $scope.testForm.testInput.$setViewValue('-11,11');
148 | expect($scope.testInput).toEqual(-11.11);
149 | expect($scope.testForm.testInput.$viewValue).toEqual('-11,11');
150 | });
151 | it('should have view value -0,11 and model value -0.11 when set -0,11', function () {
152 | $scope.testForm.testInput.$setViewValue('-0,11');
153 | expect($scope.testInput).toEqual(-0.11);
154 | expect($scope.testForm.testInput.$viewValue).toEqual('-0,11');
155 | });
156 | it('should have view value 11.11 and model value 11.11 when set 11.11', function () {
157 | $scope.testForm.testInput.$setViewValue('11.11');
158 | expect($scope.testInput).toEqual(null);
159 | expect($scope.testForm.testInput.$viewValue).toEqual('');
160 | });
161 | });
162 | describe('number format: 2 integers, decimals comma separator, positive and negative, dot thousand separator', function() {
163 | beforeEach(function(){
164 | var el = $compile('')($scope);
165 | $scope.$digest();
166 | });
167 | it('should have view value 111.111,11 and model value 111111.11 when set 111.111,11', function () {
168 | $scope.testForm.testInput.$setViewValue('111.111,11');
169 | expect($scope.testInput).toEqual(111111.11);
170 | expect($scope.testForm.testInput.$viewValue).toEqual('111.111,11');
171 | });
172 | it('should have view value -111.111,11 and model value -111111.11 when set -111.111,11', function () {
173 | $scope.testForm.testInput.$setViewValue('-111.111,11');
174 | expect($scope.testInput).toEqual(-111111.11);
175 | expect($scope.testForm.testInput.$viewValue).toEqual('-111.111,11');
176 | });
177 | });
178 | describe('number format: 2 integers, decimals dot separator, positive and negative, comma thousand separator', function() {
179 | beforeEach(function(){
180 | var el = $compile('')($scope);
181 | $scope.$digest();
182 | });
183 | it('should have view value 111,111.11 and model value 111111.11 when set 111,111.11', function () {
184 | $scope.testForm.testInput.$setViewValue('111,111.11');
185 | expect($scope.testInput).toEqual(111111.11);
186 | expect($scope.testForm.testInput.$viewValue).toEqual('111,111.11');
187 | });
188 | it('should have view value -111,111.11 and model value -111111.11 when set -111,111.11', function () {
189 | $scope.testForm.testInput.$setViewValue('-111,111.11');
190 | expect($scope.testInput).toEqual(-111111.11);
191 | expect($scope.testForm.testInput.$viewValue).toEqual('-111,111.11');
192 | });
193 | });
194 | describe('number format: integers, positive', function() {
195 | beforeEach(function(){
196 | var el = $compile('')($scope);
197 | $scope.$digest();
198 | });
199 | it('should have view value empty and model value 0 when set -', function () {
200 | $scope.testForm.testInput.$setViewValue('-');
201 | expect($scope.testInput).toEqual(null);
202 | expect($scope.testForm.testInput.$viewValue).toEqual('');
203 | });
204 | it('should have view value 0, and model value 0, when set ,', function () {
205 | $scope.testForm.testInput.$setViewValue(',');
206 | expect($scope.testInput).toEqual(null);
207 | expect($scope.testForm.testInput.$viewValue).toEqual(',');
208 | });
209 | });
210 | describe('number format: 2 integers, decimals comma separator, negative', function() {
211 | beforeEach(function(){
212 | var el = $compile('')($scope);
213 | $scope.$digest();
214 | });
215 | it('should have view value 0 and model value 0 when set 11,11', function () {
216 | $scope.testForm.testInput.$setViewValue('11,11');
217 | expect($scope.testInput).toEqual(null);
218 | expect($scope.testForm.testInput.$viewValue).toEqual('');
219 | });
220 | it('should have view value -11,11 and model value -11.11 when set -11,11', function () {
221 | $scope.testForm.testInput.$setViewValue('-11,11');
222 | expect($scope.testInput).toEqual(-11.11);
223 | expect($scope.testForm.testInput.$viewValue).toEqual('-11,11');
224 | });
225 | it('should have view value -0,11 and model value -0.11 when set -0,11', function () {
226 | $scope.testForm.testInput.$setViewValue('-0,11');
227 | expect($scope.testInput).toEqual(-0.11);
228 | expect($scope.testForm.testInput.$viewValue).toEqual('-0,11');
229 | });
230 | it('should have view value 0 and model value 0 when set 11.11', function () {
231 | $scope.testForm.testInput.$setViewValue('11.11');
232 | expect($scope.testInput).toEqual(null);
233 | expect($scope.testForm.testInput.$viewValue).toEqual('');
234 | });
235 | });
236 | describe('number format: 6 integers, decimals dot separator, positive and negative, space thousand separator', function() {
237 | beforeEach(function(){
238 | var el = $compile('')($scope);
239 | $scope.$digest();
240 | });
241 | it('should have view value 111 111.11 and model value 111111.11 when set 111 111.11', function () {
242 | $scope.testForm.testInput.$setViewValue('111 111.11');
243 | expect($scope.testInput).toEqual(111111.11);
244 | expect($scope.testForm.testInput.$viewValue).toEqual('111 111.11');
245 | });
246 | it('should have view value -111 111.11 and model value -111111.11 when set -111 111.11', function () {
247 | $scope.testForm.testInput.$setViewValue('-111 111.11');
248 | expect($scope.testInput).toEqual(-111111.11);
249 | expect($scope.testForm.testInput.$viewValue).toEqual('-111 111.11');
250 | });
251 | });
252 | describe('number format: 6 integers, decimals dot separator, positive and negative, apostrophe thousand separator', function() {
253 | beforeEach(function(){
254 | var el = $compile('')($scope);
255 | $scope.$digest();
256 | });
257 | it('should have view value 111\'111.11 and model value 111111.11 when set 111\'111.11', function () {
258 | $scope.testForm.testInput.$setViewValue('111\'111.11');
259 | expect($scope.testInput).toEqual(111111.11);
260 | expect($scope.testForm.testInput.$viewValue).toEqual('111\'111.11');
261 | });
262 | it('should have view value -111\'111.11 and model value -111111.11 when set -111\'111.11', function () {
263 | $scope.testForm.testInput.$setViewValue('-111\'111.11');
264 | expect($scope.testInput).toEqual(-111111.11);
265 | expect($scope.testForm.testInput.$viewValue).toEqual('-111\'111.11');
266 | });
267 | });
268 | describe('custom strategy provider', function(){
269 | beforeEach(function(){
270 | dynamicNumberStrategyProvider.addStrategy('price', {
271 | numInt: 6,
272 | numFract: 2,
273 | numSep: '.',
274 | numPos: true,
275 | numNeg: true,
276 | numRound: 'round',
277 | numThousand: true
278 | });
279 | var el = $compile('')($scope);
280 | $scope.$digest();
281 | });
282 | it('should have view value 11.11 and model value 11.11 when set 11.11', function () {
283 | $scope.testForm.testInput.$setViewValue('11.11');
284 | expect($scope.testInput).toEqual(11.11);
285 | expect($scope.testForm.testInput.$viewValue).toEqual('11.11');
286 | });
287 | it('should have view value 0.11 and model value 0.11 when set 0.11', function () {
288 | $scope.testForm.testInput.$setViewValue('0.11');
289 | expect($scope.testInput).toEqual(0.11);
290 | expect($scope.testForm.testInput.$viewValue).toEqual('0.11');
291 | });
292 | it('should have view value -11.11 and model value -11.11 when set -11.11', function () {
293 | $scope.testForm.testInput.$setViewValue('-11.11');
294 | expect($scope.testInput).toEqual(-11.11);
295 | expect($scope.testForm.testInput.$viewValue).toEqual('-11.11');
296 | });
297 | it('should have view value -0.11 and model value -0.11 when set -0.11', function () {
298 | $scope.testForm.testInput.$setViewValue('-0.11');
299 | expect($scope.testInput).toEqual(-0.11);
300 | expect($scope.testForm.testInput.$viewValue).toEqual('-0.11');
301 | });
302 | it('should have view value 1,111 and model value 1111 when set 11,11', function () {
303 | $scope.testForm.testInput.$setViewValue('11,11');
304 | expect($scope.testInput).toEqual(1111);
305 | expect($scope.testForm.testInput.$viewValue).toEqual('1,111');
306 | });
307 | });
308 | });
309 | describe('filter', function(){
310 | var $filter, dynamicNumberStrategyProvider;
311 | beforeEach(function() {
312 | module(function(_dynamicNumberStrategyProvider_) {
313 | dynamicNumberStrategyProvider = _dynamicNumberStrategyProvider_;
314 | });
315 | });
316 | beforeEach(inject(function(_$filter_){
317 | $filter = _$filter_;
318 | }));
319 | describe('number format: 2 integers, decimals comma separator, negative', function() {
320 | it('should return 0 when value null', function () {
321 | expect($filter('awnum')(null)).toEqual('0');
322 | });
323 | it('should return 0 when value \'abc\' ', function () {
324 | expect($filter('awnum')('abc')).toEqual('0');
325 | });
326 | it('should return 11,29 when value \'11.287\' ', function () {
327 | expect($filter('awnum')('11.287',2, ',','round')).toEqual('11,29');
328 | });
329 | it('should return 11,00 when value \'11\' and fixed fraction digit number is 2', function () {
330 | expect($filter('awnum')('11.00',2, ',','round','true')).toEqual('11,00');
331 | });
332 | });
333 | describe('number format: 6 integers, decimals comma separator, thousand dot separator', function() {
334 | it('should return 1.111,29 when value \'1111.287\' ', function () {
335 | expect($filter('awnum')('1111.287',2, ',','round','false','true')).toEqual('1.111,29');
336 | });
337 | });
338 | describe('filter with custom strategy', function() {
339 | beforeEach(function() {
340 | dynamicNumberStrategyProvider.addStrategy('price', {
341 | numFract: 1,
342 | numSep: '.',
343 | numPos: true,
344 | numNeg: true,
345 | numRound: 'round',
346 | numThousand: true
347 | });
348 | });
349 | it('should return 1,111.2 when value \'1111.16\' ', function () {
350 | expect($filter('awnum')('1111.16', 'price')).toEqual('1,111.2');
351 | });
352 | it('should return 1.111,2 when value \'1111.16\' ', function () {
353 | expect($filter('awnum')('1111.16', 'price', ',')).toEqual('1.111,2');
354 | });
355 | });
356 | });
357 | });
358 |
--------------------------------------------------------------------------------
/test/mobileDirectiveSpec.js:
--------------------------------------------------------------------------------
1 | describe('Angular-dynamic-number basic', function() {
2 | beforeEach(function(){
3 | module('dynamicNumber');
4 | });
5 | describe('directive', function(){
6 | var $compile, $scope, dynamicNumberStrategyProvider;
7 | beforeEach(function(){
8 | module(function(_dynamicNumberStrategyProvider_){
9 | dynamicNumberStrategyProvider = _dynamicNumberStrategyProvider_;
10 | });
11 | });
12 | beforeEach(function () {
13 | inject(function(_$compile_, _$rootScope_){
14 | $compile = _$compile_;
15 | $scope = _$rootScope_.$new();
16 | $scope.testInput = "";
17 | });
18 | });
19 | describe('test init values where type="tel"', function() {
20 | it('should have view value "" and model value "" when init value is ""', function () {
21 | $scope.testInput = "";
22 | $compile('')($scope);
23 | $scope.$digest();
24 |
25 | expect($scope.testInput).toEqual('');
26 | expect($scope.testForm.testInput.$viewValue).toEqual('');
27 | });
28 | it('should have view value "12" and model value 12 when init value is 12', function () {
29 | $scope.testInput = 0;
30 | $compile('')($scope);
31 | $scope.$digest();
32 |
33 | expect($scope.testInput).toEqual(0);
34 | expect($scope.testForm.testInput.$viewValue).toEqual('0');
35 | });
36 | it('should have view value "12" and model value 12 when init value is 12', function () {
37 | $scope.testInput = 12;
38 | $compile('')($scope);
39 | $scope.$digest();
40 |
41 | expect($scope.testInput).toEqual(12);
42 | expect($scope.testForm.testInput.$viewValue).toEqual('12');
43 | });
44 | it('should have view value "" and model value null when init value is null', function () {
45 | $scope.testInput = null;
46 | $compile('')($scope);
47 | $scope.$digest();
48 |
49 | expect($scope.testInput).toEqual(null);
50 | expect($scope.testForm.testInput.$viewValue).toEqual('');
51 | });
52 | it('should have view value "" and model value undefined when init value is undefined', function () {
53 | $scope.testInput = undefined;
54 | $compile('')($scope);
55 | $scope.$digest();
56 |
57 | expect($scope.testInput).toEqual(undefined);
58 | expect($scope.testForm.testInput.$viewValue).toEqual('');
59 | });
60 | //directive modify model only when user types to input. Id doesn't change it only because init model value is wrong -this is responsibility of dev to have correct init value.
61 | it('should have view value "" and model value string when init value is string', function () {
62 | $scope.testInput = 'dsads';
63 | $compile('')($scope);
64 | $scope.$digest();
65 |
66 | expect($scope.testInput).toEqual('dsads');
67 | expect($scope.testForm.testInput.$viewValue).toEqual('0');
68 | });
69 | });
70 | describe('number format: 2 integeres, decimals dot separator, positive and negative', function() {
71 | beforeEach(function(){
72 | $compile('')($scope);
73 | $scope.$digest();
74 | });
75 | it('should have view value 11.11 and model value 11.11 when set 11.11', function () {
76 | $scope.testForm.testInput.$setViewValue('11.11');
77 | expect($scope.testInput).toEqual(11.11);
78 | expect($scope.testForm.testInput.$viewValue).toEqual('11.11');
79 | });
80 | it('should have view value 0 and model value 0 when set 111.11', function () {
81 | $scope.testForm.testInput.$setViewValue('111.11');
82 | expect($scope.testInput).toEqual(null);
83 | expect($scope.testForm.testInput.$viewValue).toEqual('');
84 | });
85 | it('should have view value 0 and model value 0 when set 11.111', function () {
86 | $scope.testForm.testInput.$setViewValue('111.11');
87 | expect($scope.testInput).toEqual(null);
88 | expect($scope.testForm.testInput.$viewValue).toEqual('');
89 | });
90 | it('should have view value 0.11 and model value 0.11 when set 0.11', function () {
91 | $scope.testForm.testInput.$setViewValue('0.11');
92 | expect($scope.testInput).toEqual(0.11);
93 | expect($scope.testForm.testInput.$viewValue).toEqual('0.11');
94 | });
95 | it('should have view value 0.11 and model value 0.11 when set 00.11', function () {
96 | $scope.testForm.testInput.$setViewValue('00.11');
97 | expect($scope.testInput).toEqual(0.11);
98 | expect($scope.testForm.testInput.$viewValue).toEqual('0.11');
99 | });
100 | it('should have view value -11.11 and model value -11.11 when set -11.11', function () {
101 | $scope.testForm.testInput.$setViewValue('-11.11');
102 | expect($scope.testInput).toEqual(-11.11);
103 | expect($scope.testForm.testInput.$viewValue).toEqual('-11.11');
104 | });
105 | it('should have view value 0 and model value 0 when set -111.11', function () {
106 | $scope.testForm.testInput.$setViewValue('-111.11');
107 | expect($scope.testInput).toEqual(null);
108 | expect($scope.testForm.testInput.$viewValue).toEqual('');
109 | });
110 | it('should have view value 0 and model value 0 when set -11.111', function () {
111 | $scope.testForm.testInput.$setViewValue('-111.11');
112 | expect($scope.testInput).toEqual(null);
113 | expect($scope.testForm.testInput.$viewValue).toEqual('');
114 | });
115 | it('should have view value -0.11 and model value -0.11 when set -0.11', function () {
116 | $scope.testForm.testInput.$setViewValue('-0.11');
117 | expect($scope.testInput).toEqual(-0.11);
118 | expect($scope.testForm.testInput.$viewValue).toEqual('-0.11');
119 | });
120 | it('should have view value -0.11 and model value -0.11 when set -00.11', function () {
121 | $scope.testForm.testInput.$setViewValue('-00.11');
122 | expect($scope.testInput).toEqual(-0.11);
123 | expect($scope.testForm.testInput.$viewValue).toEqual('-0.11');
124 | });
125 | it('should have view value 0 and model value 0 when set 11,11', function () {
126 | $scope.testForm.testInput.$setViewValue('11,11');
127 | expect($scope.testInput).toEqual(null);
128 | expect($scope.testForm.testInput.$viewValue).toEqual('');
129 | });
130 | });
131 | describe('number format: 2 integeres, decimals comma separator, positive and negative', function() {
132 | beforeEach(function(){
133 | var el = $compile('')($scope);
134 | $scope.$digest();
135 | });
136 | it('should have view value 11,11 and model value 11.11 when set 11,11', function () {
137 | $scope.testForm.testInput.$setViewValue('11,11');
138 | expect($scope.testInput).toEqual(11.11);
139 | expect($scope.testForm.testInput.$viewValue).toEqual('11,11');
140 | });
141 | it('should have view value 0,11 and model value 0.11 when set 0,11', function () {
142 | $scope.testForm.testInput.$setViewValue('0,11');
143 | expect($scope.testInput).toEqual(0.11);
144 | expect($scope.testForm.testInput.$viewValue).toEqual('0,11');
145 | });
146 | it('should have view value -11,11 and model value -11.11 when set -11,11', function () {
147 | $scope.testForm.testInput.$setViewValue('-11,11');
148 | expect($scope.testInput).toEqual(-11.11);
149 | expect($scope.testForm.testInput.$viewValue).toEqual('-11,11');
150 | });
151 | it('should have view value -0,11 and model value -0.11 when set -0,11', function () {
152 | $scope.testForm.testInput.$setViewValue('-0,11');
153 | expect($scope.testInput).toEqual(-0.11);
154 | expect($scope.testForm.testInput.$viewValue).toEqual('-0,11');
155 | });
156 | it('should not have view value 0 and model value 0 when set 11.11', function () {
157 | $scope.testForm.testInput.$setViewValue('11.11');
158 | expect($scope.testInput).toEqual(null);
159 | expect($scope.testForm.testInput.$viewValue).toEqual('');
160 | });
161 | });
162 | describe('number format: 2 integeres, decimals comma separator, positive and negative, dot thousand separator', function() {
163 | beforeEach(function(){
164 | var el = $compile('')($scope);
165 | $scope.$digest();
166 | });
167 | it('should have view value 111.111,11 and model value 111111.11 when set 111.111,11', function () {
168 | $scope.testForm.testInput.$setViewValue('111.111,11');
169 | expect($scope.testInput).toEqual(111111.11);
170 | expect($scope.testForm.testInput.$viewValue).toEqual('111.111,11');
171 | });
172 | it('should have view value -111.111,11 and model value -111111.11 when set -111.111,11', function () {
173 | $scope.testForm.testInput.$setViewValue('-111.111,11');
174 | expect($scope.testInput).toEqual(-111111.11);
175 | expect($scope.testForm.testInput.$viewValue).toEqual('-111.111,11');
176 | });
177 | });
178 | describe('number format: 2 integeres, decimals dot separator, positive and negative, comma thousand separator', function() {
179 | beforeEach(function(){
180 | var el = $compile('')($scope);
181 | $scope.$digest();
182 | });
183 | it('should have view value 111,111.11 and model value 111111.11 when set 111,111.11', function () {
184 | $scope.testForm.testInput.$setViewValue('111,111.11');
185 | expect($scope.testInput).toEqual(111111.11);
186 | expect($scope.testForm.testInput.$viewValue).toEqual('111,111.11');
187 | });
188 | it('should have view value -111,111.11 and model value -111111.11 when set -111,111.11', function () {
189 | $scope.testForm.testInput.$setViewValue('-111,111.11');
190 | expect($scope.testInput).toEqual(-111111.11);
191 | expect($scope.testForm.testInput.$viewValue).toEqual('-111,111.11');
192 | });
193 | });
194 | describe('number format: 2 integeres, decimals comma separator, negative', function() {
195 | beforeEach(function(){
196 | var el = $compile('')($scope);
197 | $scope.$digest();
198 | });
199 | it('should have view value 0 and model value 0 when set 11,11', function () {
200 | $scope.testForm.testInput.$setViewValue('11,11');
201 | expect($scope.testInput).toEqual(null);
202 | expect($scope.testForm.testInput.$viewValue).toEqual('');
203 | });
204 | it('should have view value -11,11 and model value -11.11 when set -11,11', function () {
205 | $scope.testForm.testInput.$setViewValue('-11,11');
206 | expect($scope.testInput).toEqual(-11.11);
207 | expect($scope.testForm.testInput.$viewValue).toEqual('-11,11');
208 | });
209 | it('should have view value -0,11 and model value -0.11 when set -0,11', function () {
210 | $scope.testForm.testInput.$setViewValue('-0,11');
211 | expect($scope.testInput).toEqual(-0.11);
212 | expect($scope.testForm.testInput.$viewValue).toEqual('-0,11');
213 | });
214 | it('should not have view value 11.11 and model value 11.11 when set 11.11', function () {
215 | $scope.testForm.testInput.$setViewValue('11.11');
216 | expect($scope.testInput).toEqual(null);
217 | expect($scope.testForm.testInput.$viewValue).toEqual('');
218 | });
219 | });
220 | describe('number format: 6 integeres, decimals dot separator, positive and negative, space thousand separator', function() {
221 | beforeEach(function(){
222 | var el = $compile('')($scope);
223 | $scope.$digest();
224 | });
225 | it('should have view value 111 111.11 and model value 111111.11 when set 111 111.11', function () {
226 | $scope.testForm.testInput.$setViewValue('111 111.11');
227 | expect($scope.testInput).toEqual(111111.11);
228 | expect($scope.testForm.testInput.$viewValue).toEqual('111 111.11');
229 | });
230 | it('should have view value -111 111.11 and model value -111111.11 when set -111 111.11', function () {
231 | $scope.testForm.testInput.$setViewValue('-111 111.11');
232 | expect($scope.testInput).toEqual(-111111.11);
233 | expect($scope.testForm.testInput.$viewValue).toEqual('-111 111.11');
234 | });
235 | });
236 | describe('custom strategy provider', function(){
237 | beforeEach(function(){
238 | dynamicNumberStrategyProvider.addStrategy('price', {
239 | numInt: 6,
240 | numFract: 2,
241 | numSep: '.',
242 | numPos: true,
243 | numNeg: true,
244 | numRound: 'round',
245 | numThousand: true
246 | });
247 | var el = $compile('')($scope);
248 | $scope.$digest();
249 | });
250 | it('should have view value 11.11 and model value 11.11 when set 11.11', function () {
251 | $scope.testForm.testInput.$setViewValue('11.11');
252 | expect($scope.testInput).toEqual(11.11);
253 | expect($scope.testForm.testInput.$viewValue).toEqual('11.11');
254 | });
255 | it('should have view value 0.11 and model value 0.11 when set 0.11', function () {
256 | $scope.testForm.testInput.$setViewValue('0.11');
257 | expect($scope.testInput).toEqual(0.11);
258 | expect($scope.testForm.testInput.$viewValue).toEqual('0.11');
259 | });
260 | it('should have view value -11.11 and model value -11.11 when set -11.11', function () {
261 | $scope.testForm.testInput.$setViewValue('-11.11');
262 | expect($scope.testInput).toEqual(-11.11);
263 | expect($scope.testForm.testInput.$viewValue).toEqual('-11.11');
264 | });
265 | it('should have view value -0.11 and model value -0.11 when set -0.11', function () {
266 | $scope.testForm.testInput.$setViewValue('-0.11');
267 | expect($scope.testInput).toEqual(-0.11);
268 | expect($scope.testForm.testInput.$viewValue).toEqual('-0.11');
269 | });
270 | it('should not have view value 11.11 and model value 11.11 when set 11,11', function () {
271 | $scope.testForm.testInput.$setViewValue('11,11');
272 | expect($scope.testInput).not.toEqual(11.11);
273 | expect($scope.testForm.testInput.$viewValue).not.toEqual('11.11');
274 | });
275 | });
276 | });
277 | describe('filter', function(){
278 | var $filter;
279 | beforeEach(inject(function(_$filter_){
280 | $filter = _$filter_;
281 | }));
282 | describe('number format: 2 integeres, decimals comma separator, negative', function() {
283 | it('should return 0 when value null', function () {
284 | expect($filter('awnum')(null)).toEqual('0');
285 | });
286 | it('should return 0 when value \'abc\' ', function () {
287 | expect($filter('awnum')('abc')).toEqual('0');
288 | });
289 | it('should return 11,29 when value \'11.287\' ', function () {
290 | expect($filter('awnum')('11.287',2, ',','round')).toEqual('11,29');
291 | });
292 | it('should return 11,00 when value \'11\' and fixed fraction digit number is 2', function () {
293 | expect($filter('awnum')('11.00',2, ',','round','true')).toEqual('11,00');
294 | });
295 | });
296 | describe('number format: 6 integeres, decimals comma separator, thousand dot separator', function() {
297 | it('should return 1.111,29 when value \'1111.287\' ', function () {
298 | expect($filter('awnum')('1111.287',2, ',','round','false','true')).toEqual('1.111,29');
299 | });
300 | });
301 | });
302 | });
--------------------------------------------------------------------------------