├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .prettierrc.json
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── br.js
├── br.test.js
├── ch.js
├── ch.test.js
├── config
├── karma.conf.js
├── protractor.conf.js
└── test-utils.js
├── demo
├── fr.html
├── index.html
└── us.html
├── docs
├── _config.yml
└── index.md
├── fr.js
├── fr.test.js
├── gulpfile.js
├── index.js
├── index.test.js
├── package.json
├── server.js
├── src
├── angular-input-masks.br.js
├── angular-input-masks.ch.js
├── angular-input-masks.fr.js
├── angular-input-masks.js
├── angular-input-masks.us.js
├── br
│ ├── boleto-bancario
│ │ ├── boleto-bancario.js
│ │ └── boleto-bancario.test.js
│ ├── br-masks.js
│ ├── car-plate
│ │ ├── car-plate.html
│ │ ├── car-plate.js
│ │ ├── car-plate.spec.js
│ │ └── car-plate.test.js
│ ├── cep
│ │ ├── cep.html
│ │ ├── cep.js
│ │ ├── cep.spec.js
│ │ └── cep.test.js
│ ├── cnpj
│ │ ├── cnpj.html
│ │ ├── cnpj.js
│ │ ├── cnpj.spec.js
│ │ └── cnpj.test.js
│ ├── cpf-cnpj
│ │ ├── cpf-cnpj.html
│ │ ├── cpf-cnpj.js
│ │ ├── cpf-cnpj.spec.js
│ │ └── cpf-cnpj.test.js
│ ├── cpf
│ │ ├── cpf.html
│ │ ├── cpf.js
│ │ ├── cpf.spec.js
│ │ └── cpf.test.js
│ ├── inscricao-estadual
│ │ ├── ie.html
│ │ ├── ie.js
│ │ ├── ie.spec.js
│ │ └── ie.test.js
│ ├── nfe
│ │ ├── nfe.html
│ │ ├── nfe.js
│ │ ├── nfe.spec.js
│ │ └── nfe.test.js
│ ├── numero-beneficio
│ │ ├── numero-beneficio.html
│ │ ├── numero-beneficio.js
│ │ ├── numero-beneficio.spec.js
│ │ └── numero-beneficio.test.js
│ └── phone
│ │ ├── br-phone.html
│ │ ├── br-phone.js
│ │ ├── br-phone.spec.js
│ │ └── br-phone.test.js
├── ch
│ ├── ch-masks.js
│ └── phone
│ │ ├── ch-phone.html
│ │ ├── ch-phone.js
│ │ ├── ch-phone.spec.js
│ │ └── ch-phone.test.js
├── fr
│ ├── fr-masks.js
│ └── phone
│ │ ├── fr-phone.html
│ │ ├── fr-phone.js
│ │ ├── fr-phone.spec.js
│ │ └── fr-phone.test.js
├── global
│ ├── credit-card
│ │ ├── credit-card.html
│ │ ├── credit-card.js
│ │ ├── credit-card.spec.js
│ │ └── credit-card.test.js
│ ├── date
│ │ ├── date-pt-br.html
│ │ ├── date.html
│ │ ├── date.js
│ │ ├── date.spec.js
│ │ └── date.test.js
│ ├── global-masks.js
│ ├── money
│ │ ├── money.html
│ │ ├── money.js
│ │ ├── money.spec.js
│ │ └── money.test.js
│ ├── number
│ │ ├── number.html
│ │ ├── number.js
│ │ ├── number.spec.js
│ │ └── number.test.js
│ ├── percentage
│ │ ├── percentage.html
│ │ ├── percentage.js
│ │ ├── percentage.spec.js
│ │ └── percentage.test.js
│ ├── scientific-notation
│ │ ├── scientific-notation.html
│ │ ├── scientific-notation.js
│ │ ├── scientific-notation.spec.js
│ │ └── scientific-notation.test.js
│ └── time
│ │ ├── time.html
│ │ ├── time.js
│ │ ├── time.spec.js
│ │ └── time.test.js
├── helpers
│ ├── mask-factory.js
│ ├── number-mask-builder.js
│ ├── pre-formatters.js
│ └── validators.js
└── us
│ ├── phone
│ ├── us-phone.html
│ ├── us-phone.js
│ ├── us-phone.spec.js
│ └── us-phone.test.js
│ └── us-masks.js
├── us.js
└── us.test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | indent_size = 4
8 | indent_style = tab
9 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | !node_modules
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true,
6 | "node": true,
7 | "jasmine": true,
8 | "protractor": true
9 | },
10 | "globals": {
11 | "angular": true,
12 | "TestUtil": true
13 | },
14 | "extends": "eslint:recommended",
15 | "rules": {
16 | "accessor-pairs": 2,
17 | "array-bracket-spacing": [2, "never"],
18 | "array-callback-return": 2,
19 | "arrow-body-style": 2,
20 | "arrow-parens": [2, "always"],
21 | "arrow-spacing": [
22 | 2,
23 | {
24 | "after": true,
25 | "before": true
26 | }
27 | ],
28 | "block-scoped-var": 0,
29 | "block-spacing": 2,
30 | "brace-style": [2, "1tbs"],
31 | "callback-return": 2,
32 | "camelcase": 2,
33 | "comma-dangle": [2, "only-multiline"],
34 | "comma-spacing": 0,
35 | "comma-style": [2, "last"],
36 | "complexity": 2,
37 | "computed-property-spacing": [2, "never"],
38 | "consistent-this": 2,
39 | "curly": 2,
40 | "default-case": 2,
41 | "dot-location": [2, "property"],
42 | "dot-notation": 2,
43 | "eol-last": 0,
44 | "eqeqeq": 2,
45 | "func-names": 0,
46 | "func-style": 0,
47 | "generator-star-spacing": 2,
48 | "global-require": 0,
49 | "guard-for-in": 2,
50 | "handle-callback-err": 2,
51 | "id-blacklist": 2,
52 | "id-length": 0,
53 | "id-match": 2,
54 | "indent": [
55 | 2,
56 | "tab",
57 | {
58 | "SwitchCase": 1
59 | }
60 | ],
61 | "init-declarations": 0,
62 | "key-spacing": 0,
63 | "keyword-spacing": 2,
64 | "linebreak-style": [2, "unix"],
65 | "lines-around-comment": 2,
66 | "max-depth": 2,
67 | "max-len": 0,
68 | "max-nested-callbacks": 2,
69 | "max-params": 0,
70 | "new-cap": 2,
71 | "new-parens": 2,
72 | "newline-after-var": 0,
73 | "newline-per-chained-call": 0,
74 | "no-alert": 2,
75 | "no-array-constructor": 2,
76 | "no-bitwise": 0,
77 | "no-caller": 2,
78 | "no-catch-shadow": 2,
79 | "no-confusing-arrow": 2,
80 | "no-continue": 2,
81 | "no-div-regex": 2,
82 | "no-else-return": 2,
83 | "no-empty-function": 2,
84 | "no-eq-null": 2,
85 | "no-eval": 2,
86 | "no-extend-native": 2,
87 | "no-extra-bind": 2,
88 | "no-extra-label": 2,
89 | "no-extra-parens": 0,
90 | "no-floating-decimal": 2,
91 | "no-implicit-globals": 2,
92 | "no-implied-eval": 2,
93 | "no-inner-declarations": [2, "functions"],
94 | "no-invalid-this": 2,
95 | "no-iterator": 2,
96 | "no-label-var": 2,
97 | "no-labels": 2,
98 | "no-lone-blocks": 2,
99 | "no-lonely-if": 2,
100 | "no-loop-func": 2,
101 | "no-magic-numbers": 0,
102 | "no-mixed-requires": 2,
103 | "no-multi-spaces": 0,
104 | "no-multi-str": 2,
105 | "no-multiple-empty-lines": 2,
106 | "no-native-reassign": 2,
107 | "no-negated-condition": 0,
108 | "no-nested-ternary": 2,
109 | "no-new": 2,
110 | "no-new-func": 2,
111 | "no-new-object": 2,
112 | "no-new-require": 2,
113 | "no-new-wrappers": 2,
114 | "no-octal-escape": 2,
115 | "no-param-reassign": 0,
116 | "no-path-concat": 2,
117 | "no-plusplus": 0,
118 | "no-process-env": 2,
119 | "no-process-exit": 2,
120 | "no-proto": 2,
121 | "no-restricted-imports": 2,
122 | "no-restricted-modules": 2,
123 | "no-restricted-syntax": 2,
124 | "no-return-assign": 2,
125 | "no-script-url": 2,
126 | "no-self-compare": 2,
127 | "no-sequences": 2,
128 | "no-shadow": 0,
129 | "no-shadow-restricted-names": 2,
130 | "no-spaced-func": 2,
131 | "no-sync": 2,
132 | "no-ternary": 0,
133 | "no-throw-literal": 2,
134 | "no-trailing-spaces": 2,
135 | "no-undef-init": 2,
136 | "no-undefined": 2,
137 | "no-underscore-dangle": 2,
138 | "no-unmodified-loop-condition": 2,
139 | "no-unneeded-ternary": 2,
140 | "no-unused-expressions": 2,
141 | "no-useless-call": 2,
142 | "no-useless-concat": 2,
143 | "no-useless-constructor": 2,
144 | "no-useless-escape": "off",
145 | "no-var": 0,
146 | "no-void": 2,
147 | "no-warning-comments": [
148 | 2,
149 | {
150 | "location": "start"
151 | }
152 | ],
153 | "no-whitespace-before-property": 2,
154 | "no-with": 2,
155 | "object-curly-spacing": [2, "never"],
156 | "object-shorthand": 0,
157 | "one-var": 0,
158 | "one-var-declaration-per-line": 0,
159 | "operator-assignment": [2, "always"],
160 | "operator-linebreak": 2,
161 | "padded-blocks": 0,
162 | "prefer-arrow-callback": 0,
163 | "prefer-const": 2,
164 | "prefer-reflect": 0,
165 | "prefer-rest-params": 2,
166 | "prefer-spread": 2,
167 | "prefer-template": 0,
168 | "quote-props": 0,
169 | "quotes": [2, "single"],
170 | "radix": [2, "as-needed"],
171 | "require-jsdoc": 0,
172 | "require-yield": 2,
173 | "semi": [2, "always"],
174 | "semi-spacing": [
175 | 2,
176 | {
177 | "after": true,
178 | "before": false
179 | }
180 | ],
181 | "sort-imports": 2,
182 | "sort-vars": 0,
183 | "space-before-blocks": [2, "always"],
184 | "space-before-function-paren": [2, "never"],
185 | "space-in-parens": [2, "never"],
186 | "space-infix-ops": 0,
187 | "space-unary-ops": 2,
188 | "spaced-comment": [2, "never"],
189 | "strict": [2, "global"],
190 | "template-curly-spacing": 2,
191 | "valid-jsdoc": 2,
192 | "vars-on-top": 0,
193 | "wrap-iife": 2,
194 | "wrap-regex": 0,
195 | "yield-star-spacing": 2,
196 | "yoda": [2, "never"]
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [10.x, 12.x, 14.x]
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v1
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | - run: npm install
28 | - run: npm run lint
29 | - run: npm run build --if-present
30 | - run: npm run test:unit
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | bower-angular-input-masks
3 | /node_modules
4 | coverage
5 | package-lock.json
6 | /*.log
7 | releases
8 | .tern-project
9 | .DS_Store
10 | *.swp
11 | .idea
12 | *.iml
13 | /.vs
14 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | dist: trusty
3 | addons:
4 | apt:
5 | sources:
6 | - google-chrome
7 | packages:
8 | - google-chrome-stable
9 | language: node_js
10 | cache:
11 | directories:
12 | - node_modules # NPM packages
13 | node_js:
14 | - 10
15 | before_script:
16 | - export DISPLAY=:99.0
17 | - sh -e /etc/init.d/xvfb start
18 | install:
19 | - npm install
20 | script:
21 | - npm run ci
22 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at igor.rafael@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | First of all, but not less important, thank you for taking your time to contribute with this project.
2 | Your help is very apreciated and welcome! =)
3 |
4 | # Pull requests
5 |
6 | To speed up the pull request review process, please check if your PR follows the tips below:
7 | - if it is a bug fix implement at least one test case that exhibits the wrong behavior that you are fixing
8 | - if it is a new feature try to implement tests that at least cover the more important parts of your code
9 | - check if the code coverage is not being reduced
10 | - squash your commits into a single commit
11 | - follow the [angular commit convention] (https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines)
12 |
13 | # Bug reporting
14 |
15 | Please include in your bug report the following information:
16 | - a brief description of the bug in the title (avoid writing generic titles)
17 | - which version of angular-input-masks and angular was used
18 | - in which browsers (and their versions) the bug was observed
19 | - optional: pull request with a test case that reproduces the bug =)
20 |
21 | # Implementing tests
22 |
23 | This project implements two kinds of tests:
24 | - unit tests: using [Karma](http://karma-runner.github.io) + [Jasmine](http://jasmine.github.io/) (Files: src/**/*.test.js)
25 | - e2e tests: using [Protractor](https://github.com/angular/protractor) + Jasmine (Files: src/**/*.spec.js)
26 |
27 | Only implement an e2e test with you can not reproduce it with an unit test.
28 |
29 | # Where this project need more love
30 |
31 | - Documentation
32 | - Reduce the lib size without obfuscating the code
33 | - Internationalization
34 | - Build options
35 | - Unit tests
36 | - Include more browsers in the test setup
37 | - And new input-masks =)
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Igor Rafael
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular-input-masks [](https://travis-ci.org/assisrafael/angular-input-masks) [](https://coveralls.io/r/assisrafael/angular-input-masks?branch=master) [](https://github.com/conventional-changelog/standard-version)
2 |
3 | [](https://nodei.co/npm/angular-input-masks/)
4 |
5 | [](https://gitter.im/assisrafael/angular-input-masks?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://www.bountysource.com/teams/angular-input-masks/bounties?utm_source=angular-input-masks&utm_medium=shield&utm_campaign=bounties_posted)
6 |
7 | Opinionated angular input masks. Provides ready to use masks with little (br/inscricao-estadual) to no configuration (number, cnpj, etc).
8 |
9 | ### Compatibility
10 |
11 | - angular-input-masks@~2: angular@^1.3 (however is only tested with lastest 1.x version) and [ECMAScript 5 compliant browsers](http://kangax.github.io/compat-table/es5/) (however CI only tests chrome and firefox).
12 | - angular-input-masks@~1: angular@~1.2
13 |
14 |
15 | ## Installation
16 |
17 | ```
18 | npm install --save angular-input-masks
19 | ```
20 |
21 | ## Configuration
22 |
23 | ### Without browserify:
24 |
25 | 1. Import the ```angular-input-masks-standalone.min.js``` script in your page. For example:
26 |
27 | ```
28 |
29 | ```
30 |
31 | Obs: for npm the build scripts are available inside ```releases``` folder.
32 |
33 | 2. Include the module name ```ui.utils.masks``` in your angular app. For example:
34 |
35 | ```
36 | angular.module('app', ['ui.utils.masks']);
37 | ```
38 |
39 | ### With browserify:
40 |
41 | ```
42 | angular.module('demo', [require('angular-input-masks')]);
43 | ```
44 |
45 | ## Internationalization
46 |
47 | Some masks are internationalized, so you need to include the proper angular-locale in your app(see: https://docs.angularjs.org/guide/i18n).
48 |
49 | ## How to use
50 |
51 | - Number mask Example :
52 |
53 | ```html
54 |
55 | ```
56 |
57 | - Define the number of decimals (default is 2):
58 |
59 | ```html
60 |
61 | ```
62 |
63 | ### More examples ###
64 |
65 | _See more usage examples in the [Demo page](http://assisrafael.github.io/angular-input-masks/)_
66 |
67 |
68 | ## Other build options
69 |
70 | If you are using npm (without browserify):
71 |
72 | - angular-input-masks-dependencies.js: provides all external dependencies (string-mask, br-validations, momentjs)
73 | - angular-input-masks-br.js: provides only global and BR directives, and does not include external dependencies (string-mask, br-validations, momentjs)
74 | - angular-input-masks-us.js: provides only global and US directives, and does not include external dependencies (string-mask, br-validations, momentjs)
75 | - angular-input-masks-fr.js: provides only global and FR directives, and does not include external dependencies (string-mask, br-validations, momentjs)
76 | - angular-input-masks.js: provides all directives, and does not include external dependencies (string-mask, br-validations, momentjs)
77 |
78 | If you are using npm with browserify:
79 |
80 | - ```require('angular-input-masks')```: provides all directives
81 | - ```require('angular-input-masks/br')```: only global and BR directives
82 | - ```require('angular-input-masks/us')```: only global and US directives
83 | - ```require('angular-input-masks/fr')```: only global and FR directives
84 |
85 | ## Filters
86 |
87 | Looking for related filters? Take a look at [angular-br-filters](https://github.com/the-darc/angular-br-filters)
88 |
89 | ## Build
90 |
91 | ```
92 | npm install
93 | npm run build
94 | ```
95 |
96 | ### Tests
97 |
98 | - Unit:
99 | - Uses [Karma](http://karma-runner.github.io) + [Jasmine](http://jasmine.github.io/)
100 | - Files: `src/**/*.test.js`
101 |
102 | ```
103 | npm run test:unit
104 | ```
105 |
106 | - e2e:
107 | - Uses [Protractor](https://github.com/angular/protractor) + Jasmine
108 | - Files: `src/**/*.spec.js`
109 |
110 |
111 | ```
112 | npm run test:e2e
113 | ```
114 |
115 | - To run both tests:
116 |
117 | ```
118 | npm test
119 | ```
120 |
--------------------------------------------------------------------------------
/br.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./src/angular-input-masks.br');
4 |
--------------------------------------------------------------------------------
/br.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('angular-input-masks-standalone', function() {
4 | var moduleName = require('./br.js');
5 |
6 | beforeEach(angular.mock.module('ui.utils.masks'));
7 |
8 | it('should export the module name', function() {
9 | expect(moduleName).toBe('ui.utils.masks');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/ch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./src/angular-input-masks.ch');
4 |
--------------------------------------------------------------------------------
/ch.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('angular-input-masks-standalone', function() {
4 | var moduleName = require('./ch.js');
5 |
6 | beforeEach(angular.mock.module('ui.utils.masks'));
7 |
8 | it('should export the module name', function() {
9 | expect(moduleName).toBe('ui.utils.masks');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/config/karma.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*eslint no-process-env: 0*/
4 |
5 | var path = require('path');
6 |
7 | module.exports = function(config) {
8 | var configuration = {
9 | basePath: path.join(__dirname, '..'),
10 | frameworks: ['browserify', 'jasmine'],
11 | files: [
12 | 'node_modules/angular/angular.js',
13 | 'node_modules/angular-mocks/angular-mocks.js',
14 | 'config/test-utils.js',
15 | 'src/**/*.test.js',
16 | '*.test.js'
17 | ],
18 | port: 9876,
19 | reporters: ['progress', 'coverage'],
20 | preprocessors: {
21 | 'src/**/*.test.js': ['browserify'],
22 | '*.test.js': ['browserify'],
23 | 'src/**/!(*test).js': ['coverage']
24 | },
25 | browserify: {
26 | debug: true,
27 | transform: [
28 | [
29 | 'browserify-istanbul', {
30 | ignore: '**/*.test.js'
31 | }
32 | ]
33 | ]
34 | },
35 | coverageReporter: {
36 | dir: 'coverage',
37 | reporters: [{
38 | type: 'lcov',
39 | subdir: 'report-lcov'
40 | }, {
41 | type: 'html',
42 | subdir: 'report-html'
43 | }, {
44 | type: 'text',
45 | }, {
46 | type: 'text-summary',
47 | }]
48 | },
49 | colors: true,
50 | autoWatch: false,
51 | singleRun: false,
52 | browsers: ['ChromeHeadless'],
53 | customLaunchers: {
54 | 'Chrome_travis_ci': {
55 | base: 'Chrome',
56 | flags: ['--no-sandbox']
57 | }
58 | },
59 | };
60 |
61 | if (process.env.TRAVIS) {
62 | configuration.browsers = ['Chrome_travis_ci'];
63 |
64 | configuration.reporters.push('coveralls');
65 | }
66 |
67 | config.set(configuration);
68 | };
69 |
--------------------------------------------------------------------------------
/config/protractor.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*eslint no-process-env: 0*/
4 | /*eslint no-console: 0*/
5 | /*eslint no-empty-function: 0*/
6 |
7 | const {SpecReporter} = require('jasmine-spec-reporter');
8 |
9 | const PORT = process.env.PORT || 9090;
10 |
11 | var config = {
12 | allScriptsTimeout: 11000,
13 | directConnect: true,
14 | capabilities: {
15 | browserName: 'chrome'
16 | },
17 | framework: 'jasmine',
18 | jasmineNodeOpts: {
19 | showColors: true,
20 | defaultTimeoutInterval: 30000,
21 | print() {}
22 | },
23 | specs: [
24 | '../src/**/*.spec.js'
25 | ],
26 | baseUrl: `http://localhost:${PORT}/demo`,
27 | beforeLaunch() {
28 | var server = require('../server');
29 |
30 | return new Promise((resolve) => {
31 | server.listen(PORT, function() {
32 | console.log(`Server running in port ${PORT}`);
33 | resolve();
34 | });
35 | });
36 | },
37 | onPrepare() {
38 | jasmine.getEnv().addReporter(new SpecReporter({
39 | spec: {
40 | displayStacktrace: true
41 | }
42 | }));
43 | }
44 | };
45 |
46 | if (process.env.CI) {
47 | config.capabilities.chromeOptions = {
48 | args: ['--headless', '--disable-gpu', '--window-size=800x600']
49 | };
50 | }
51 |
52 | exports.config = config;
53 |
--------------------------------------------------------------------------------
/config/test-utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*global inject*/
4 |
5 | var TestUtil = {
6 | compile: function(html, initialScope) {
7 | var container;
8 |
9 | inject(function($compile, $rootScope) {
10 | if (angular.isDefined(initialScope)) {
11 | angular.extend($rootScope, initialScope);
12 | }
13 |
14 | container = $compile(html)($rootScope);
15 | $rootScope.$apply();
16 | });
17 |
18 | return container;
19 | }
20 | };
21 |
22 | if (module) {
23 | module.exports = TestUtil;
24 | }
25 |
--------------------------------------------------------------------------------
/demo/fr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Mask FR Demo
6 |
7 |
8 |
9 |
15 |
16 |
17 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/demo/us.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Mask US Demo
6 |
7 |
8 |
9 |
15 |
16 |
17 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | title: angular-input-masks
2 | description: Opinionated input masks for AngularJS
3 | google_analytics:
4 | show_downloads: true
5 | theme: jekyll-theme-cayman
6 |
7 | gems:
8 | - jekyll-mentions
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # How to use
2 |
3 | ## ui-number-mask ##
4 |
5 | ```html
6 |
7 | ```
8 |
9 | - Define the number of decimals (default is 2):
10 |
11 | ```html
12 |
13 | ```
14 |
15 | - Allow negative numbers using the ```ui-negative-number``` attribute:
16 |
17 | ```html
18 |
19 | ```
20 |
21 | - Support to the ```min```, ```max``` and ```ui-hide-group-sep``` attributes.
22 |
23 | ```html
24 |
25 | ```
26 |
27 | ```html
28 |
29 |
30 | ```
31 |
32 | - Internationalized: Used the decimal separator and the thousands separator defined in the client browser configuration.
33 |
34 |
35 | ## ui-percentage-mask ##
36 |
37 | - Example:
38 |
39 | ```html
40 |
41 | ```
42 |
43 | - You can set the number of decimals (default is 2):
44 |
45 | ```html
46 |
47 | ```
48 |
49 | - The $modelValue is the $viewValue / 100, so $viewValue - 100% = $modelValue - 1
50 |
51 | - You can use the same value in $modelValue and $viewValue using ```ui-percentage-value```:
52 |
53 | ```html
54 |
55 | ```
56 |
57 | - Support to the ```min```, ```max``` and ```ui-hide-group-sep``` attributes.
58 |
59 | - Internationalized: Used the decimal separator and thousands separator defined in the client browser configuration.
60 |
61 | - The $modelValue is the $viewValue / 100, so $viewValue - 100% = $modelValue - 1
62 |
63 | - You can add ```ui-hide-space``` attribute to hide space between [NUMBER] and %
64 |
65 |
66 | ## ui-money-mask ##
67 |
68 | - Example:
69 |
70 | ```html
71 |
72 | ```
73 |
74 | - Define the number of decimals (default is 2):
75 |
76 | ```html
77 |
78 | ```
79 |
80 | - Support to the ```min```, ```max``` and ```ui-hide-group-sep``` attributes.
81 |
82 | - Internationalized: Used the currency symbol, decimal separator and thousands separator defined in the client browser configuration.
83 |
84 | - You can add ```ui-hide-space``` attribute to hide space between [Currency symbol] and [NUMBER]
85 |
86 |
87 | ## ui-br-phone-number ##
88 | ```html
89 |
90 | ```
91 | * Suports only *simple* phone number format: `1234-5678` || `12345-6789`
92 | ```html
93 |
94 | ```
95 | * Suports only *area code* phone number format: `(12) 3456-7890` || `(12) 34567-8901`
96 | ```html
97 |
98 | ```
99 | * Suports only *country code* phone number format: `+12 (34) 5678-9012` || `+12 (34) 56789-0123`
100 | ```html
101 |
102 | ```
103 |
104 | ## ui-us-phone-number ##
105 |
106 | * Example:
107 |
108 | ```html
109 |
110 | ```
111 |
112 | * Outputs phone number in the following format: (123) 456-7890
113 |
114 | ## ui-br-cep-mask ##
115 | ```html
116 |
117 | ```
118 |
119 |
120 | ## ui-br-cpf-mask ##
121 |
122 | - Example:
123 |
124 | ```html
125 |
126 | ```
127 |
128 |
129 | ## ui-br-cnpj-mask ##
130 |
131 | - Example:
132 |
133 | ```html
134 |
135 | ```
136 |
137 |
138 | ## ui-br-cpfcnpj-mask ##
139 |
140 | - Example:
141 |
142 | ```html
143 |
144 | ```
145 |
146 | ## ui-br-numero-beneficio-mask ##
147 |
148 | - Example:
149 |
150 | ```html
151 |
152 | ```
153 |
154 | - Máscara o número do benefício do INSS que segue o formato 999.999.999-9.
155 |
156 |
157 | ## ui-br-ie-mask ##
158 | ```html
159 |
160 |
161 | ```
162 | - Support masks for all the 27 brazillian states.
163 |
164 | - Validations according to the [Sintegra especification](http://www.sintegra.gov.br/insc_est.html).
165 |
166 |
167 | ## ui-time-mask ##
168 | -Example:
169 |
170 | ```html
171 |
172 | ```
173 | - Support to the ```short``` attributes.
174 | ```html
175 |
176 | ```
177 |
178 |
179 | ## ui-date-mask ##
180 | -Example:
181 |
182 | ```html
183 |
184 | ```
185 | - Support to the custom date masks (See moment.js date formats).
186 | ```html
187 |
188 | ```
189 | - Support to ```parse``` attribute. When the attribute is set to ```false```, the inputed value will be passed to the model as a string. Default value of the attribute is ```true```.
190 | ```html
191 |
192 | ```
193 |
194 | ## time-mask ##
195 | -Example:
196 |
197 | ```html
198 |
199 | ```
200 | ```html
201 |
202 | ```
203 |
204 | ## Masks to be documentated (help wanted!)
205 |
206 | - ui-nfe-access-key-mask
207 | - ui-time-mask
208 | - ui-date-mask
209 | - ui-br-boleto-bancario-mask
210 | - ui-br-car-plate-mask
211 | - ui-scientific-notation-mask
212 | - ui-fr-phone-number
213 |
--------------------------------------------------------------------------------
/fr.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./src/angular-input-masks.fr');
4 |
--------------------------------------------------------------------------------
/fr.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('angular-input-masks-standalone', function() {
4 | var moduleName = require('./fr.js');
5 |
6 | beforeEach(angular.mock.module('ui.utils.masks'));
7 |
8 | it('should export the module name', function() {
9 | expect(moduleName).toBe('ui.utils.masks');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*eslint no-console: 0*/
4 | /*eslint no-process-env: 0*/
5 |
6 | const path = require('path');
7 |
8 | const gulp = require('gulp'),
9 | mergeStream = require('merge-stream'),
10 | browserify = require('browserify'),
11 | source = require('vinyl-source-stream'),
12 | buffer = require('vinyl-buffer'),
13 | loadPlugins = require('gulp-load-plugins');
14 |
15 | const plugins = loadPlugins({
16 | config: path.join(__dirname, 'package.json')
17 | });
18 |
19 | const pkg = require('./package.json');
20 |
21 | const header = [
22 | '/**',
23 | ' * <%= pkg.name %>',
24 | ' * <%= pkg.description %>',
25 | ' * @version v<%= pkg.version %>',
26 | ' * @link <%= pkg.homepage %>',
27 | ' * @license <%= pkg.license %>',
28 | ' */',
29 | ''
30 | ].join('\n');
31 |
32 | exports.build = gulp.series(buildDependencies, build);
33 |
34 | exports.default = gulp.series(build, function() {
35 | gulp.watch('src/**/*.js', build);
36 | });
37 |
38 | exports.serve = gulp.series(build, function(done) {
39 | var server = require('./server');
40 |
41 | const PORT = process.env.PORT || 9090;
42 | server.listen(PORT, function() {
43 | console.log(`Server running in port ${PORT}`);
44 | done();
45 | });
46 | });
47 |
48 | function buildDependencies() {
49 | return browserify()
50 | .require('string-mask', {
51 | expose: 'string-mask'
52 | })
53 | .require('date-fns/format', {
54 | expose: 'date-fns/format'
55 | })
56 | .require('date-fns/parse', {
57 | expose: 'date-fns/parse'
58 | })
59 | .require('date-fns/isValid', {
60 | expose: 'date-fns/isValid'
61 | })
62 | .require('br-validations', {
63 | expose: 'br-validations'
64 | })
65 | .bundle()
66 | .pipe(source('angular-input-masks-dependencies.js'))
67 | .pipe(buffer())
68 | .pipe(gulp.dest('./releases/'))
69 | .pipe(plugins.uglify())
70 | .pipe(
71 | plugins.rename({
72 | extname: '.min.js'
73 | })
74 | )
75 | .pipe(gulp.dest('./releases/'));
76 | }
77 |
78 | function build() {
79 | var files = [
80 | {
81 | fileName: 'angular-input-masks.js',
82 | debug: false,
83 | bundleExternal: false
84 | },
85 | {
86 | fileName: 'angular-input-masks.br.js',
87 | debug: false,
88 | bundleExternal: false
89 | },
90 | {
91 | fileName: 'angular-input-masks.ch.js',
92 | debug: false,
93 | bundleExternal: false
94 | },
95 | {
96 | fileName: 'angular-input-masks.fr.js',
97 | debug: false,
98 | bundleExternal: false
99 | },
100 | {
101 | fileName: 'angular-input-masks.us.js',
102 | debug: false,
103 | bundleExternal: false
104 | },
105 | {
106 | fileName: 'angular-input-masks.js',
107 | outputFileName: 'angular-input-masks-standalone.js',
108 | debug: false,
109 | bundleExternal: true
110 | },
111 | {
112 | fileName: 'angular-input-masks.js',
113 | outputFileName: 'angular-input-masks-debug.js',
114 | debug: true,
115 | bundleExternal: true
116 | }
117 | ];
118 |
119 | var tasks = files.map(function(entry) {
120 | return browserify({
121 | entries: entry.fileName,
122 | detectGlobals: false,
123 | basedir: './src/',
124 | debug: entry.debug,
125 | bundleExternal: entry.bundleExternal
126 | })
127 | .bundle()
128 | .pipe(source(entry.outputFileName || entry.fileName))
129 | .pipe(buffer())
130 | .pipe(plugins.header(header, { pkg: pkg }))
131 | .pipe(gulp.dest('./releases/'))
132 | .pipe(plugins.uglify())
133 | .pipe(
134 | plugins.rename({
135 | extname: '.min.js'
136 | })
137 | )
138 | .pipe(gulp.dest('./releases/'));
139 | });
140 |
141 | return mergeStream(tasks);
142 | }
143 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var moduleName = require('./src/angular-input-masks.js');
4 |
5 | module.exports = moduleName;
6 |
--------------------------------------------------------------------------------
/index.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('angular-input-masks-standalone', function() {
4 | var moduleName = require('./index.js');
5 |
6 | beforeEach(angular.mock.module('ui.utils.masks'));
7 |
8 | it('should export the module name', function() {
9 | expect(moduleName).toBe('ui.utils.masks');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-input-masks",
3 | "version": "4.4.1",
4 | "description": "Personalized input masks for AngularJS",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/assisrafael/angular-input-masks.git"
8 | },
9 | "homepage": "http://github.com/assisrafael/angular-input-masks",
10 | "scripts": {
11 | "build": "gulp build",
12 | "ci": "npm run lint && npm run webdriver && npm run test",
13 | "demo": "gulp serve",
14 | "lint": "eslint *.js src/ config/ --fix",
15 | "test": "npm run test:unit && npm run test:e2e",
16 | "pretest:e2e": "npm run build",
17 | "test:e2e": "protractor config/protractor.conf.js",
18 | "test:unit": "karma start config/karma.conf.js --single-run",
19 | "test:unit:watch": "karma start config/karma.conf.js --auto-watch",
20 | "prepare": "npm run build",
21 | "release": "standard-version",
22 | "webdriver": "webdriver-manager update"
23 | },
24 | "keywords": [
25 | "input",
26 | "mask",
27 | "angularjs"
28 | ],
29 | "author": "igor.rafael@gmail.com",
30 | "license": "MIT",
31 | "dependencies": {
32 | "br-validations": "^0.3.1",
33 | "date-fns": "2.0.0-alpha.7",
34 | "string-mask": "^0.3.0"
35 | },
36 | "devDependencies": {
37 | "angular": "^1.7.8",
38 | "angular-i18n": "^1.7.8",
39 | "angular-mocks": "^1.7.8",
40 | "browserify": "^16.2.3",
41 | "browserify-istanbul": "^3.0.1",
42 | "eslint": "^5.16.0",
43 | "express": "^4.16.4",
44 | "gulp": "^4.0.2",
45 | "gulp-header": "^2.0.7",
46 | "gulp-load-plugins": "^1.5.0",
47 | "gulp-rename": "^1.4.0",
48 | "gulp-uglify": "^3.0.2",
49 | "istanbul": "^0.4.5",
50 | "jasmine-core": "^3.4.0",
51 | "jasmine-spec-reporter": "^4.2.1",
52 | "karma": "^4.1.0",
53 | "karma-browserify": "^6.0.0",
54 | "karma-chrome-launcher": "^2.2.0",
55 | "karma-coverage": "^1.1.2",
56 | "karma-coveralls": "^2.1.0",
57 | "karma-jasmine": "^2.0.1",
58 | "merge-stream": "^1.0.0",
59 | "minimist": "^1.1.1",
60 | "protractor": "^5.4.2",
61 | "standard-version": "^6.0.1",
62 | "vinyl-buffer": "^1.0.0",
63 | "vinyl-source-stream": "^2.0.0",
64 | "webdriver-manager": "^12.1.4"
65 | },
66 | "files": [
67 | "src/",
68 | "releases/",
69 | "index.js",
70 | "br.js",
71 | "ch.js",
72 | "fr.js",
73 | "us.js",
74 | "LICENSE",
75 | "CHANGELOG.md",
76 | "README.md"
77 | ]
78 | }
79 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*eslint no-console: 0*/
4 |
5 | var express = require('express');
6 | var server = express();
7 |
8 | server.use(express.static('./'));
9 |
10 | module.exports = server;
11 |
12 |
--------------------------------------------------------------------------------
/src/angular-input-masks.br.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = angular.module('ui.utils.masks', [
4 | require('./global/global-masks'),
5 | require('./br/br-masks')
6 | ]).name;
7 |
--------------------------------------------------------------------------------
/src/angular-input-masks.ch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = angular.module('ui.utils.masks', [
4 | require('./global/global-masks'),
5 | require('./ch/ch-masks')
6 | ]).name;
7 |
--------------------------------------------------------------------------------
/src/angular-input-masks.fr.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = angular.module('ui.utils.masks', [
4 | require('./global/global-masks'),
5 | require('./fr/fr-masks')
6 | ]).name;
7 |
--------------------------------------------------------------------------------
/src/angular-input-masks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = angular.module('ui.utils.masks', [
4 | require('./global/global-masks'),
5 | require('./br/br-masks'),
6 | require('./ch/ch-masks'),
7 | require('./fr/fr-masks'),
8 | require('./us/us-masks')
9 | ]).name;
10 |
--------------------------------------------------------------------------------
/src/angular-input-masks.us.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = angular.module('ui.utils.masks', [
4 | require('./global/global-masks'),
5 | require('./us/us-masks')
6 | ]).name;
7 |
--------------------------------------------------------------------------------
/src/br/boleto-bancario/boleto-bancario.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var maskFactory = require('../../helpers/mask-factory');
5 |
6 | var boletoBancarioMask = new StringMask('00000.00000 00000.000000 00000.000000 0 00000000000000');
7 | var tributoBancarioMask = new StringMask('00000000000-0 00000000000-0 00000000000-0 00000000000-0');
8 |
9 | module.exports = maskFactory({
10 | clearValue: function (rawValue) {
11 | return rawValue.replace(/[^0-9]/g, '').slice(0, 48);
12 | },
13 | format: function (cleanValue) {
14 | if (cleanValue.length === 0) {
15 | return cleanValue;
16 | }
17 | if (cleanValue[0] === '8')
18 | return tributoBancarioMask.apply(cleanValue).replace(/[^0-9]$/, '');
19 | return boletoBancarioMask.apply(cleanValue).replace(/[^0-9]$/, '');
20 | },
21 | validations: {
22 | brBoletoBancario: function (value) {
23 | return [47, 48].indexOf(value.length) >= 0;
24 | }
25 | }
26 | });
27 |
--------------------------------------------------------------------------------
/src/br/boleto-bancario/boleto-bancario.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../br-masks');
4 |
5 | describe('ui-br-boleto-bancario-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.br'));
7 |
8 | it('should format initial model values', function() {
9 | var input = TestUtil.compile('', {
10 | model: '34958723405162304548623240917012593348590495345'
11 | });
12 |
13 | var model = input.controller('ngModel');
14 | expect(model.$viewValue).toBe('34958.72340 51623.045486 23240.917012 5 93348590495345');
15 | });
16 |
17 | it('should format initial model values to tax bank slip number', function () {
18 | var input = TestUtil.compile('', {
19 | model: '858100000021280003281907210720190118389990980104'
20 | });
21 |
22 | var model = input.controller('ngModel');
23 | expect(model.$viewValue).toBe('85810000002-1 28000328190-7 21072019011-8 38999098010-4');
24 | });
25 |
26 | it('should ignore non digits', function() {
27 | var input = TestUtil.compile('');
29 | var model = input.controller('ngModel');
30 |
31 | var tests = [
32 | {value:'@', viewValue:'', modelValue:''},
33 | {value:'2-', viewValue:'2', modelValue:'2'},
34 | {value:'23a', viewValue:'23', modelValue:'23'},
35 | {value:'23_346', viewValue:'23346', modelValue:'23346'},
36 | {value:'23346!324', viewValue:'23346.324', modelValue:'23346324'},
37 | {value:'23346!324sdfg9870', viewValue:'23346.32498 70', modelValue:'233463249870'},
38 | ];
39 |
40 | tests.forEach(function(test) {
41 | input.val(test.value).triggerHandler('input');
42 | expect(model.$viewValue).toBe(test.viewValue);
43 | expect(model.$modelValue).toBe(test.modelValue);
44 | });
45 | });
46 |
47 | it('should validate a "boleto bancário"', function() {
48 | var input = TestUtil.compile('', {
49 | model: '104914433855119000002000000001413252'
50 | });
51 |
52 | var model = input.controller('ngModel');
53 | expect(model.$error.brBoletoBancario).toBe(true);
54 | input.val('10491443385511900000200000000141325230000093423').triggerHandler('input');
55 | expect(model.$error.brBoletoBancario).toBeUndefined();
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/src/br/br-masks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var m = angular.module('ui.utils.masks.br', [])
4 | .directive('uiBrBoletoBancarioMask', require('./boleto-bancario/boleto-bancario'))
5 | .directive('uiBrCarPlateMask', require('./car-plate/car-plate'))
6 | .directive('uiBrCepMask', require('./cep/cep'))
7 | .directive('uiBrCnpjMask', require('./cnpj/cnpj'))
8 | .directive('uiBrCpfMask', require('./cpf/cpf'))
9 | .directive('uiBrCpfcnpjMask', require('./cpf-cnpj/cpf-cnpj'))
10 | .directive('uiBrNumeroBeneficioMask', require('./numero-beneficio/numero-beneficio'))
11 | .directive('uiBrIeMask', require('./inscricao-estadual/ie'))
12 | .directive('uiNfeAccessKeyMask', require('./nfe/nfe'))
13 | .directive('uiBrPhoneNumberMask', require('./phone/br-phone'));
14 |
15 | module.exports = m.name;
16 |
--------------------------------------------------------------------------------
/src/br/car-plate/car-plate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Car Plate Spec
6 |
7 |
8 |
9 |
15 |
16 |
17 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/br/car-plate/car-plate.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var maskFactory = require('../../helpers/mask-factory');
5 |
6 | var carPlateMask = new StringMask('UUU-0000');
7 |
8 | module.exports = maskFactory({
9 | clearValue: function(rawValue) {
10 | return rawValue.replace(/[^a-zA-Z0-9]/g, '').slice(0, 7);
11 | },
12 | format: function(cleanValue) {
13 | return (carPlateMask.apply(cleanValue) || '').replace(/[^a-zA-Z0-9]$/, '');
14 | },
15 | validations: {
16 | carPlate: function(value) {
17 | return value.length === 7;
18 | }
19 | }
20 | });
21 |
--------------------------------------------------------------------------------
/src/br/car-plate/car-plate.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiBrCarPlateMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/br/car-plate/car-plate.html');
6 | expect(browser.getTitle()).toEqual('Car Plate Spec');
7 | });
8 |
9 | it('should apply a Car Plate mask while the user is typping:', function() {
10 | var BS = protractor.Key.BACK_SPACE;
11 |
12 | var tests = [
13 | {key:'@', viewValue:'', modelValue:''},
14 | {key:'A', viewValue:'A', modelValue:'A'},
15 | {key:'B', viewValue:'AB', modelValue:'AB'},
16 | {key:'C', viewValue:'ABC', modelValue:'ABC'},
17 | {key:'-', viewValue:'ABC', modelValue:'ABC'},
18 | {key:'2', viewValue:'ABC-2', modelValue:'ABC2'},
19 | {key:'0', viewValue:'ABC-20', modelValue:'ABC20'},
20 | {key:'1', viewValue:'ABC-201', modelValue:'ABC201'},
21 | {key:'0', viewValue:'ABC-2010', modelValue:'ABC2010'},
22 | {key:'9', viewValue:'ABC-2010', modelValue:'ABC2010'},
23 | {key:BS, viewValue:'ABC-201', modelValue:'ABC201'},
24 | {key:BS, viewValue:'ABC-20', modelValue:'ABC20'},
25 | {key:BS, viewValue:'ABC-2', modelValue:'ABC2'},
26 | {key:BS, viewValue:'ABC', modelValue:'ABC'},
27 | {key:BS, viewValue:'AB', modelValue:'AB'},
28 | {key:BS, viewValue:'A', modelValue:'A'},
29 | ];
30 |
31 | var input = element(by.model('carPlate')),
32 | value = element(by.binding('carPlate'));
33 |
34 | for (var i = 0; i < tests.length; i++) {
35 | input.sendKeys(tests[i].key);
36 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
37 | expect(value.getText()).toEqual(tests[i].modelValue);
38 | }
39 | });
40 |
41 | it('should apply a Car Plate mask in a model with default value:', function() {
42 | var input = element(by.model('initializedCarPlate'));
43 |
44 | expect(input.getAttribute('value')).toEqual('ABC-2010');
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/br/car-plate/car-plate.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../br-masks');
4 |
5 | describe('ui-br-car-plate-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.br'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: 'ABC2010'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('ABC-2010');
32 | });
33 |
34 | it('should accept formatted initial model values', function() {
35 | var input = TestUtil.compile('', {
36 | model: 'ABC-2010'
37 | });
38 |
39 | var model = input.controller('ngModel');
40 | expect(model.$viewValue).toBe('ABC-2010');
41 | });
42 |
43 | it('should ignore non digits', function() {
44 | var input = TestUtil.compile('');
46 | var model = input.controller('ngModel');
47 |
48 | var tests = [
49 | {value:'@', viewValue:'', modelValue:''},
50 | {value:'A-', viewValue:'A', modelValue:'A'},
51 | {value:'ABC_2', viewValue:'ABC-2', modelValue:'ABC2'},
52 | {value:'ABC!2', viewValue:'ABC-2', modelValue:'ABC2'},
53 | {value:'ABC!2324', viewValue:'ABC-2324', modelValue:'ABC2324'},
54 | ];
55 |
56 | tests.forEach(function(test) {
57 | input.val(test.value).triggerHandler('input');
58 | expect(model.$viewValue).toBe(test.viewValue);
59 | expect(model.$modelValue).toBe(test.modelValue);
60 | });
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/src/br/cep/cep.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CEP Spec
6 |
7 |
8 |
9 |
15 |
16 |
17 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/br/cep/cep.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var maskFactory = require('../../helpers/mask-factory');
5 |
6 | var cepMask = new StringMask('00000-000');
7 |
8 | module.exports = maskFactory({
9 | clearValue: function(rawValue) {
10 | return rawValue.toString().replace(/[^0-9]/g, '').slice(0, 8);
11 | },
12 | format: function(cleanValue) {
13 | return (cepMask.apply(cleanValue) || '').replace(/[^0-9]$/, '');
14 | },
15 | validations: {
16 | cep: function(value) {
17 | return value.toString().trim().length === 8;
18 | }
19 | }
20 | });
21 |
--------------------------------------------------------------------------------
/src/br/cep/cep.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiBrCepMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/br/cep/cep.html');
6 | expect(browser.getTitle()).toEqual('CEP Spec');
7 | });
8 |
9 | it('should apply a CEP mask while the user is typping:', function() {
10 | var BS = protractor.Key.BACK_SPACE;
11 |
12 | var tests = [
13 | {key:'@', viewValue:'', modelValue:''},
14 | {key:'3', viewValue:'3', modelValue:'3'},
15 | {key:'0', viewValue:'30', modelValue:'30'},
16 | {key:'1', viewValue:'301', modelValue:'301'},
17 | {key:'1', viewValue:'3011', modelValue:'3011'},
18 | {key:'2', viewValue:'30112', modelValue:'30112'},
19 | {key:'-', viewValue:'30112', modelValue:'30112'},
20 | {key:'0', viewValue:'30112-0', modelValue:'301120'},
21 | {key:'1', viewValue:'30112-01', modelValue:'3011201'},
22 | {key:'0', viewValue:'30112-010', modelValue:'30112010'},
23 | {key:'9', viewValue:'30112-010', modelValue:'30112010'},
24 | {key:BS, viewValue:'30112-01', modelValue:'3011201'},
25 | {key:BS, viewValue:'30112-0', modelValue:'301120'},
26 | {key:BS, viewValue:'30112', modelValue:'30112'},
27 | {key:BS, viewValue:'3011', modelValue:'3011'},
28 | {key:BS, viewValue:'301', modelValue:'301'},
29 | {key:BS, viewValue:'30', modelValue:'30'},
30 | {key:BS, viewValue:'3', modelValue:'3'}
31 | ];
32 |
33 | var input = element(by.model('cep')),
34 | value = element(by.binding('cep'));
35 |
36 | for (var i = 0; i < tests.length; i++) {
37 | input.sendKeys(tests[i].key);
38 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
39 | expect(value.getText()).toEqual(tests[i].modelValue);
40 | }
41 | });
42 |
43 | it('should apply a CEP mask in a model with default value:', function() {
44 | var input = element(by.model('initializedCep'));
45 |
46 | expect(input.getAttribute('value')).toEqual('30112-010');
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/src/br/cep/cep.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../br-masks');
4 |
5 | describe('ui-br-cep-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.br'));
7 |
8 | it('cep validation should fail without the fix', function() {
9 | var input = TestUtil.compile('', {
10 | model: 30112010
11 | });
12 | var model = input.controller('ngModel');
13 |
14 | expect(model.$valid).toBe(true);
15 | });
16 |
17 | it('should throw an error if used without ng-model', function() {
18 | expect(function() {
19 | TestUtil.compile('');
20 | }).toThrow();
21 | });
22 |
23 | it('should register a $parser and a $formatter', function() {
24 | var input = TestUtil.compile('');
25 | var model = input.controller('ngModel');
26 |
27 | var maskedInput = TestUtil.compile('');
28 | var maskedModel = maskedInput.controller('ngModel');
29 |
30 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
31 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
32 | });
33 |
34 | it('should convert number inputs to correct format', function() {
35 | var input = TestUtil.compile('', {
36 | model: 30112010
37 | });
38 |
39 | var model = input.controller('ngModel');
40 | expect(model.$viewValue).toBe('30112-010');
41 | });
42 |
43 | it('should format initial model values', function() {
44 | var input = TestUtil.compile('', {
45 | model: '30112010'
46 | });
47 |
48 | var model = input.controller('ngModel');
49 | expect(model.$viewValue).toBe('30112-010');
50 | });
51 |
52 | it('should accept formatted initial model values', function() {
53 | var input = TestUtil.compile('', {
54 | model: '30112-010'
55 | });
56 |
57 | var model = input.controller('ngModel');
58 | expect(model.$viewValue).toBe('30112-010');
59 | });
60 |
61 | it('should ignore non digits', function() {
62 | var input = TestUtil.compile('');
63 | var model = input.controller('ngModel');
64 |
65 | var tests = [
66 | {value:'@', viewValue:'', modelValue:''},
67 | {value:'2-', viewValue:'2', modelValue:'2'},
68 | {value:'23a', viewValue:'23', modelValue:'23'},
69 | {value:'23_34', viewValue:'2334', modelValue:'2334'},
70 | {value:'23346!', viewValue:'23346', modelValue:'23346'},
71 | {value:'23346!324', viewValue:'23346-324', modelValue:'23346324'},
72 | ];
73 |
74 | tests.forEach(function(test) {
75 | input.val(test.value).triggerHandler('input');
76 | expect(model.$viewValue).toBe(test.viewValue);
77 | expect(model.$modelValue).toBe(test.modelValue);
78 | });
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/src/br/cnpj/cnpj.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CNPJ Spec
6 |
7 |
8 |
9 |
15 |
16 |
17 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/br/cnpj/cnpj.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var BrV = require('br-validations');
5 |
6 | var maskFactory = require('../../helpers/mask-factory');
7 |
8 | var cnpjPattern = new StringMask('00.000.000\/0000-00');
9 |
10 | module.exports = maskFactory({
11 | clearValue: function(rawValue) {
12 | return rawValue.replace(/[^\d]/g, '').slice(0, 14);
13 | },
14 | format: function(cleanValue) {
15 | return (cnpjPattern.apply(cleanValue) || '').trim().replace(/[^0-9]$/, '');
16 | },
17 | validations: {
18 | cnpj: function(value) {
19 | return BrV.cnpj.validate(value);
20 | }
21 | }
22 | });
23 |
--------------------------------------------------------------------------------
/src/br/cnpj/cnpj.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiBrCnpjMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/br/cnpj/cnpj.html');
6 | expect(browser.getTitle()).toEqual('CNPJ Spec');
7 | });
8 |
9 | it('should apply a CNPJ mask while the user is typping:', function() {
10 | var BS = protractor.Key.BACK_SPACE;
11 |
12 | var tests = [
13 | {key:'1', viewValue:'1', modelValue: '1'},
14 | {key:'3', viewValue:'13', modelValue: '13'},
15 | {key:'8', viewValue:'13.8', modelValue: '138'},
16 | {key:'8', viewValue:'13.88', modelValue: '1388'},
17 | {key:'3', viewValue:'13.883', modelValue: '13883'},
18 | {key:'8', viewValue:'13.883.8', modelValue: '138838'},
19 | {key:'7', viewValue:'13.883.87', modelValue: '1388387'},
20 | {key:'5', viewValue:'13.883.875', modelValue: '13883875'},
21 | {key:'0', viewValue:'13.883.875/0', modelValue: '138838750'},
22 | {key:'0', viewValue:'13.883.875/00', modelValue: '1388387500'},
23 | {key:'0', viewValue:'13.883.875/000', modelValue: '13883875000'},
24 | {key:'1', viewValue:'13.883.875/0001', modelValue: '138838750001'},
25 | {key:'2', viewValue:'13.883.875/0001-2', modelValue: '1388387500012'},
26 | {key:'0', viewValue:'13.883.875/0001-20', modelValue: '13883875000120'},
27 | {key:'0', viewValue:'13.883.875/0001-20', modelValue: '13883875000120'},
28 | {key:BS, viewValue:'13.883.875/0001-2', modelValue: '1388387500012'},
29 | {key:BS, viewValue:'13.883.875/0001', modelValue: '138838750001'},
30 | {key:BS, viewValue:'13.883.875/000', modelValue: '13883875000'},
31 | {key:BS, viewValue:'13.883.875/00', modelValue: '1388387500'},
32 | {key:BS, viewValue:'13.883.875/0', modelValue: '138838750'},
33 | {key:BS, viewValue:'13.883.875', modelValue: '13883875'},
34 | {key:BS, viewValue:'13.883.87', modelValue: '1388387'},
35 | {key:BS, viewValue:'13.883.8', modelValue: '138838'},
36 | {key:BS, viewValue:'13.883', modelValue: '13883'},
37 | {key:BS, viewValue:'13.88', modelValue: '1388'},
38 | {key:BS, viewValue:'13.8', modelValue: '138'},
39 | {key:BS, viewValue:'13', modelValue: '13'},
40 | {key:BS, viewValue:'1', modelValue: '1'}
41 | ];
42 |
43 | var input = element(by.model('fieldCnpj')),
44 | value = element(by.binding('fieldCnpj'));
45 |
46 | for (var i = 0; i < tests.length; i++) {
47 | input.sendKeys(tests[i].key);
48 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
49 | expect(value.getText()).toEqual(tests[i].modelValue);
50 | }
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/src/br/cnpj/cnpj.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../br-masks');
4 |
5 | describe('ui-br-cnpj-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.br'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: '13883875000120'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('13.883.875/0001-20');
32 | });
33 |
34 | it('should handle corner cases', angular.mock.inject(function($rootScope) {
35 | var input = TestUtil.compile('');
36 | var model = input.controller('ngModel');
37 |
38 | var tests = [
39 | {modelValue: '', viewValue: ''},
40 | {modelValue: '0', viewValue: '0'},
41 | {modelValue: null, viewValue: null},
42 | {}, //tests undefined values
43 | ];
44 |
45 | tests.forEach(function(test) {
46 | $rootScope.model = test.modelValue;
47 | $rootScope.$digest();
48 | expect(model.$viewValue).toBe(test.viewValue);
49 | });
50 | }));
51 |
52 | it('should ignore non digits', function() {
53 | var input = TestUtil.compile('');
54 | var model = input.controller('ngModel');
55 |
56 | var tests = [
57 | {value:'@', viewValue:'', modelValue:''},
58 | {value:'13', viewValue:'13', modelValue:'13'},
59 | {value:'13$8', viewValue:'13.8', modelValue:'138'},
60 | {value:'13$88#3', viewValue:'13.883', modelValue:'13883'},
61 | {value:'13$88#3875', viewValue:'13.883.875', modelValue:'13883875'},
62 | {value:'13$88#3875-0', viewValue:'13.883.875/0', modelValue:'138838750'},
63 | {value:'13$88#3875-0001', viewValue:'13.883.875/0001', modelValue:'138838750001'},
64 | {value:'13$88#3875-000120', viewValue:'13.883.875/0001-20', modelValue:'13883875000120'},
65 | ];
66 |
67 | tests.forEach(function(test) {
68 | input.val(test.value).triggerHandler('input');
69 | expect(model.$viewValue).toBe(test.viewValue);
70 | expect(model.$modelValue).toBe(test.modelValue);
71 | });
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/src/br/cpf-cnpj/cpf-cnpj.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CPF-CNPJ Spec
6 |
7 |
8 |
9 |
16 |
17 |
18 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/br/cpf-cnpj/cpf-cnpj.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var BrV = require('br-validations');
5 | var maskFactory = require('../../helpers/mask-factory');
6 |
7 | var cnpjPattern = new StringMask('00.000.000\/0000-00');
8 | var cpfPattern = new StringMask('000.000.000-00');
9 |
10 | module.exports = maskFactory({
11 | clearValue: function(rawValue) {
12 | return rawValue.replace(/[^\d]/g, '').slice(0, 14);
13 | },
14 | format: function(cleanValue) {
15 | var formatedValue;
16 |
17 | if (cleanValue.length > 11) {
18 | formatedValue = cnpjPattern.apply(cleanValue);
19 | } else {
20 | formatedValue = cpfPattern.apply(cleanValue) || '';
21 | }
22 |
23 | return formatedValue.trim().replace(/[^0-9]$/, '');
24 | },
25 | validations: {
26 | cpf: function(value) {
27 | return value.length > 11 || BrV.cpf.validate(value);
28 | },
29 | cnpj: function(value) {
30 | return value.length <= 11 || BrV.cnpj.validate(value);
31 | }
32 | }
33 | });
34 |
--------------------------------------------------------------------------------
/src/br/cpf-cnpj/cpf-cnpj.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiBrCpfcnpjMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/br/cpf-cnpj/cpf-cnpj.html');
6 | expect(browser.getTitle()).toEqual('CPF-CNPJ Spec');
7 | });
8 |
9 | it('should apply a CPF/CNPJ mask while the user is typping:', function() {
10 | var BS = protractor.Key.BACK_SPACE;
11 |
12 | var tests = [
13 | {key:'1', viewValue:'1', modelValue: '1'},
14 | {key:'3', viewValue:'13', modelValue: '13'},
15 | {key:'8', viewValue:'138', modelValue: '138'},
16 | {key:'8', viewValue:'138.8', modelValue: '1388'},
17 | {key:'3', viewValue:'138.83', modelValue: '13883'},
18 | {key:'8', viewValue:'138.838', modelValue: '138838'},
19 | {key:'7', viewValue:'138.838.7', modelValue: '1388387'},
20 | {key:'5', viewValue:'138.838.75', modelValue: '13883875'},
21 | {key:'0', viewValue:'138.838.750', modelValue: '138838750'},
22 | {key:'0', viewValue:'138.838.750-0', modelValue: '1388387500'},
23 | {key:'0', viewValue:'138.838.750-00', modelValue: '13883875000'},
24 |
25 | {key:'1', viewValue:'13.883.875/0001', modelValue: '138838750001'},
26 | {key:'2', viewValue:'13.883.875/0001-2', modelValue: '1388387500012'},
27 | {key:'0', viewValue:'13.883.875/0001-20', modelValue: '13883875000120'},
28 | {key:'0', viewValue:'13.883.875/0001-20', modelValue: '13883875000120'},
29 | {key:BS, viewValue:'13.883.875/0001-2', modelValue: '1388387500012'},
30 | {key:BS, viewValue:'13.883.875/0001', modelValue: '138838750001'},
31 |
32 | {key:BS, viewValue:'138.838.750-00', modelValue: '13883875000'},
33 | {key:BS, viewValue:'138.838.750-0', modelValue: '1388387500'},
34 | {key:BS, viewValue:'138.838.750', modelValue: '138838750'},
35 | {key:BS, viewValue:'138.838.75', modelValue: '13883875'},
36 | {key:BS, viewValue:'138.838.7', modelValue: '1388387'},
37 | {key:BS, viewValue:'138.838', modelValue: '138838'},
38 | {key:BS, viewValue:'138.83', modelValue: '13883'},
39 | {key:BS, viewValue:'138.8', modelValue: '1388'},
40 | {key:BS, viewValue:'138', modelValue: '138'},
41 | {key:BS, viewValue:'13', modelValue: '13'},
42 | {key:BS, viewValue:'1', modelValue: '1'}
43 | ];
44 |
45 | var input = element(by.model('cpfcnpj')),
46 | value = element(by.binding('cpfcnpj'));
47 |
48 | for (var i = 0; i < tests.length; i++) {
49 | input.sendKeys(tests[i].key);
50 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
51 | expect(value.getText()).toEqual(tests[i].modelValue);
52 | }
53 | });
54 |
55 | it('should apply a CPFCNPJ mask in a model with default CPF value:', function() {
56 | var input = element(by.model('initializedCpfCnpj1'));
57 |
58 | expect(input.getAttribute('value')).toEqual('563.383.329-58');
59 | });
60 |
61 | it('should apply a CPFCNPJ mask in a model with default CNPJ value:', function() {
62 | var input = element(by.model('initializedCpfCnpj2'));
63 |
64 | expect(input.getAttribute('value')).toEqual('23.212.161/0001-44');
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/src/br/cpf-cnpj/cpf-cnpj.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../br-masks');
4 |
5 | describe('ui-br-cpfcnpj-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.br'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: '35244457640'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('352.444.576-40');
32 | });
33 |
34 | it('should handle corner cases', angular.mock.inject(function($rootScope) {
35 | var input = TestUtil.compile('');
36 | var model = input.controller('ngModel');
37 |
38 | var tests = [
39 | {modelValue: '', viewValue: ''},
40 | {modelValue: '0', viewValue: '0'},
41 | {modelValue: null, viewValue: null},
42 | {}, //tests undefined values
43 | ];
44 |
45 | tests.forEach(function(test) {
46 | $rootScope.model = test.modelValue;
47 | $rootScope.$digest();
48 | expect(model.$viewValue).toBe(test.viewValue);
49 | });
50 | }));
51 |
52 | it('should ignore non digits', function() {
53 | var input = TestUtil.compile('');
55 | var model = input.controller('ngModel');
56 |
57 | var tests = [
58 | {value:'@', viewValue:'', modelValue:''},
59 | {value:'3!', viewValue:'3', modelValue:'3'},
60 | {value:'35$2', viewValue:'352', modelValue:'352'},
61 | {value:'35$24%4.', viewValue:'352.44', modelValue:'35244'},
62 | {value:'35$24%445&', viewValue:'352.444.5', modelValue:'3524445'},
63 | {value:'35$24%445&76', viewValue:'352.444.576', modelValue:'352444576'},
64 | {value:'35$24%445&7640', viewValue:'352.444.576-40', modelValue:'35244457640'},
65 | {value:'13$88#3875-000120', viewValue:'13.883.875/0001-20', modelValue:'13883875000120'},
66 | {value:'13$88#3875-0001', viewValue:'13.883.875/0001', modelValue:'138838750001'},
67 | ];
68 |
69 | tests.forEach(function(test) {
70 | input.val(test.value).triggerHandler('input');
71 | expect(model.$viewValue).toBe(test.viewValue);
72 | expect(model.$modelValue).toBe(test.modelValue);
73 | });
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/src/br/cpf/cpf.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CPF Spec
6 |
7 |
8 |
9 |
15 |
16 |
17 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/br/cpf/cpf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var BrV = require('br-validations');
5 |
6 | var maskFactory = require('../../helpers/mask-factory');
7 |
8 | var cpfPattern = new StringMask('000.000.000-00');
9 |
10 | module.exports = maskFactory({
11 | clearValue: function(rawValue) {
12 | return rawValue.replace(/[^\d]/g, '').slice(0, 11);
13 | },
14 | format: function(cleanValue) {
15 | return (cpfPattern.apply(cleanValue) || '').trim().replace(/[^0-9]$/, '');
16 | },
17 | validations: {
18 | cpf: function(value) {
19 | return BrV.cpf.validate(value);
20 | }
21 | }
22 | });
23 |
--------------------------------------------------------------------------------
/src/br/cpf/cpf.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiBrCpfMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/br/cpf/cpf.html');
6 | expect(browser.getTitle()).toEqual('CPF Spec');
7 | });
8 |
9 | it('should apply a CPF mask while the user is typping:', function() {
10 | var BS = protractor.Key.BACK_SPACE;
11 |
12 | var tests = [
13 | {key:'-', viewValue:'', modelValue: ''},
14 | {key:'3', viewValue:'3', modelValue: '3'},
15 | {key:'5', viewValue:'35', modelValue: '35'},
16 | {key:'2', viewValue:'352', modelValue: '352'},
17 | {key:'4', viewValue:'352.4', modelValue: '3524'},
18 | {key:'4', viewValue:'352.44', modelValue: '35244'},
19 | {key:'4', viewValue:'352.444', modelValue: '352444'},
20 | {key:'5', viewValue:'352.444.5', modelValue: '3524445'},
21 | {key:'7', viewValue:'352.444.57', modelValue: '35244457'},
22 | {key:'6', viewValue:'352.444.576', modelValue: '352444576'},
23 | {key:'4', viewValue:'352.444.576-4', modelValue: '3524445764'},
24 | {key:'0', viewValue:'352.444.576-40', modelValue: '35244457640'},
25 | {key:'9', viewValue:'352.444.576-40', modelValue: '35244457640'},
26 | {key:BS, viewValue:'352.444.576-4', modelValue: '3524445764'},
27 | {key:BS, viewValue:'352.444.576', modelValue: '352444576'},
28 | {key:BS, viewValue:'352.444.57', modelValue: '35244457'},
29 | {key:BS, viewValue:'352.444.5', modelValue: '3524445'},
30 | {key:BS, viewValue:'352.444', modelValue: '352444'},
31 | {key:BS, viewValue:'352.44', modelValue: '35244'},
32 | {key:BS, viewValue:'352.4', modelValue: '3524'},
33 | {key:BS, viewValue:'352', modelValue: '352'},
34 | {key:BS, viewValue:'35', modelValue: '35'},
35 | {key:BS, viewValue:'3', modelValue: '3'},
36 | ];
37 |
38 | var input = element(by.model('fieldCpf')),
39 | value = element(by.binding('fieldCpf'));
40 |
41 | for (var i = 0; i < tests.length; i++) {
42 | input.sendKeys(tests[i].key);
43 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
44 | expect(value.getText()).toEqual(tests[i].modelValue);
45 | }
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/src/br/cpf/cpf.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../br-masks');
4 |
5 | describe('ui-br-cpf-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.br'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: '35244457640'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('352.444.576-40');
32 | });
33 |
34 | it('should handle corner cases', angular.mock.inject(function($rootScope) {
35 | var input = TestUtil.compile('');
36 | var model = input.controller('ngModel');
37 |
38 | var tests = [
39 | {modelValue: '', viewValue: ''},
40 | {modelValue: '0', viewValue: '0'},
41 | {modelValue: null, viewValue: null},
42 | {}, //tests undefined values
43 | ];
44 |
45 | tests.forEach(function(test) {
46 | $rootScope.model = test.modelValue;
47 | $rootScope.$digest();
48 | expect(model.$viewValue).toBe(test.viewValue);
49 | });
50 | }));
51 |
52 | it('should ignore non digits', function() {
53 | var input = TestUtil.compile('');
54 | var model = input.controller('ngModel');
55 |
56 | var tests = [
57 | {value:'@', viewValue:'', modelValue:''},
58 | {value:'3!', viewValue:'3', modelValue:'3'},
59 | {value:'35$2', viewValue:'352', modelValue:'352'},
60 | {value:'35$24%4.', viewValue:'352.44', modelValue:'35244'},
61 | {value:'35$24%445&', viewValue:'352.444.5', modelValue:'3524445'},
62 | {value:'35$24%445&76', viewValue:'352.444.576', modelValue:'352444576'},
63 | {value:'35$24%445&7640', viewValue:'352.444.576-40', modelValue:'35244457640'},
64 | ];
65 |
66 | tests.forEach(function(test) {
67 | input.val(test.value).triggerHandler('input');
68 | expect(model.$viewValue).toBe(test.viewValue);
69 | expect(model.$modelValue).toBe(test.modelValue);
70 | });
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/src/br/inscricao-estadual/ie.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Inscricão Estadual Spec
6 |
7 |
8 |
9 |
20 |
21 |
22 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/br/inscricao-estadual/ie.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var BrV = require('br-validations');
5 |
6 | var ieMasks = {
7 | 'AC': [{mask: new StringMask('00.000.000/000-00')}],
8 | 'AL': [{mask: new StringMask('000000000')}],
9 | 'AM': [{mask: new StringMask('00.000.000-0')}],
10 | 'AP': [{mask: new StringMask('000000000')}],
11 | 'BA': [{chars: 8, mask: new StringMask('000000-00')}, {mask: new StringMask('0000000-00')}],
12 | 'CE': [{mask: new StringMask('00000000-0')}],
13 | 'DF': [{mask: new StringMask('00000000000-00')}],
14 | 'ES': [{mask: new StringMask('00000000-0')}],
15 | 'GO': [{mask: new StringMask('00.000.000-0')}],
16 | 'MA': [{mask: new StringMask('000000000')}],
17 | 'MG': [{mask: new StringMask('000.000.000/0000')}],
18 | 'MS': [{mask: new StringMask('000000000')}],
19 | 'MT': [{mask: new StringMask('0000000000-0')}],
20 | 'PA': [{mask: new StringMask('00-000000-0')}],
21 | 'PB': [{mask: new StringMask('00000000-0')}],
22 | 'PE': [{chars: 9, mask: new StringMask('0000000-00')}, {mask: new StringMask('00.0.000.0000000-0')}],
23 | 'PI': [{mask: new StringMask('000000000')}],
24 | 'PR': [{mask: new StringMask('000.00000-00')}],
25 | 'RJ': [{mask: new StringMask('00.000.00-0')}],
26 | 'RN': [{chars: 9, mask: new StringMask('00.000.000-0')}, {mask: new StringMask('00.0.000.000-0')}],
27 | 'RO': [{mask: new StringMask('0000000000000-0')}],
28 | 'RR': [{mask: new StringMask('00000000-0')}],
29 | 'RS': [{mask: new StringMask('000/0000000')}],
30 | 'SC': [{mask: new StringMask('000.000.000')}],
31 | 'SE': [{mask: new StringMask('00000000-0')}],
32 | 'SP': [{mask: new StringMask('000.000.000.000')}, {mask: new StringMask('-00000000.0/000')}],
33 | 'TO': [{mask: new StringMask('00000000000')}]
34 | };
35 |
36 | function BrIeMaskDirective($parse) {
37 | function clearValue(value) {
38 | if (!value) {
39 | return value;
40 | }
41 |
42 | return value.replace(/[^0-9]/g, '');
43 | }
44 |
45 | function getMask(uf, value) {
46 | if (!uf || !ieMasks[uf]) {
47 | return;
48 | }
49 |
50 | if (uf === 'SP' && /^P/i.test(value)) {
51 | return ieMasks.SP[1].mask;
52 | }
53 |
54 | var masks = ieMasks[uf];
55 | var i = 0;
56 | while (masks[i].chars && masks[i].chars < clearValue(value).length && i < masks.length - 1) {
57 | i++;
58 | }
59 |
60 | return masks[i].mask;
61 | }
62 |
63 | function applyIEMask(value, uf) {
64 | var mask = getMask(uf, value);
65 |
66 | if (!mask) {
67 | return value;
68 | }
69 |
70 | var processed = mask.process(clearValue(value));
71 | var formatedValue = processed.result || '';
72 | formatedValue = formatedValue.trim().replace(/[^0-9]$/, '');
73 |
74 | if (uf === 'SP' && /^p/i.test(value)) {
75 | return 'P' + formatedValue;
76 | }
77 |
78 | return formatedValue;
79 | }
80 |
81 | return {
82 | restrict: 'A',
83 | require: 'ngModel',
84 | link: function(scope, element, attrs, ctrl) {
85 | var state = ($parse(attrs.uiBrIeMask)(scope) || '').toUpperCase();
86 |
87 | function formatter(value) {
88 | if (ctrl.$isEmpty(value)) {
89 | return value;
90 | }
91 |
92 | return applyIEMask(value, state);
93 | }
94 |
95 | function parser(value) {
96 | if (ctrl.$isEmpty(value)) {
97 | return value;
98 | }
99 |
100 | var formatedValue = applyIEMask(value, state);
101 | var actualValue = clearValue(formatedValue);
102 |
103 | if (ctrl.$viewValue !== formatedValue) {
104 | ctrl.$setViewValue(formatedValue);
105 | ctrl.$render();
106 | }
107 |
108 | if (state && state.toUpperCase() === 'SP' && /^p/i.test(value)) {
109 | return 'P' + actualValue;
110 | }
111 |
112 | return actualValue;
113 | }
114 |
115 | ctrl.$formatters.push(formatter);
116 | ctrl.$parsers.push(parser);
117 |
118 | ctrl.$validators.ie = function validator(modelValue) {
119 | return ctrl.$isEmpty(modelValue) || BrV.ie(state).validate(modelValue);
120 | };
121 |
122 | scope.$watch(attrs.uiBrIeMask, function(newState) {
123 | state = (newState || '').toUpperCase();
124 |
125 | parser(ctrl.$viewValue);
126 | ctrl.$validate();
127 | });
128 | }
129 | };
130 | }
131 | BrIeMaskDirective.$inject = ['$parse'];
132 |
133 | module.exports = BrIeMaskDirective;
134 |
--------------------------------------------------------------------------------
/src/br/inscricao-estadual/ie.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiBrIeMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/br/inscricao-estadual/ie.html');
6 | expect(browser.getTitle()).toEqual('Inscricão Estadual Spec');
7 | });
8 |
9 | it('should apply a IE mask in a model with default value:', function() {
10 | var input = element(by.model('initializedIE'));
11 |
12 | expect(input.getAttribute('value')).toEqual('P-35887477.0/971');
13 | });
14 |
15 | it('should validate in a model with default value', function() {
16 | var valid = element(by.binding('form.field19.$error'));
17 |
18 | expect(valid.getText()).toEqual('{}');
19 | });
20 |
21 | it('should not have validation errors when empty', function() {
22 | var inputIE = element(by.model('inscEst')),
23 | inputUF = element(by.model('state')),
24 | valid = element(by.binding('form.field20.$error'));
25 |
26 | for (var i = 1; i < 27; i++) {
27 | inputIE.clear();
28 | inputUF.all(by.tagName('option')).get(i).click();
29 | expect(valid.getText()).toEqual('{}');
30 | inputIE.sendKeys(1);
31 | expect(valid.getText()).toEqual('{ "ie": true }');
32 | }
33 | });
34 |
35 | it('should be valid if the model is a valid I.E', function() {
36 | var inputIE = element(by.model('inscEst')),
37 | inputUF = element(by.model('state')),
38 | valid = element(by.binding('form.field20.$error'));
39 |
40 | inputUF.all(by.tagName('option')).get(26).click();
41 | inputIE.clear();
42 | inputIE.sendKeys('P-35887477.0/971');
43 | expect(valid.getText()).toEqual('{}');
44 | });
45 |
46 | var tests = [
47 | {uf:'AC', option: 1, modelValue: '0100482300112', viewValue: '01.004.823/001-12'},
48 | {uf:'AL', option: 2, modelValue: '240000048', viewValue: '240000048'},
49 | {uf:'AM', option: 3, modelValue: '198712308', viewValue: '19.871.230-8'},
50 | {uf:'AP', option: 4, modelValue: '030123459', viewValue: '030123459'},
51 | {uf:'BA', option: 5, modelValue: '090493871', viewValue: '090493-87', viewValue2: '0904938-71', valid: true},
52 | {uf:'CE', option: 6, modelValue: '060000015', viewValue: '06000001-5'},
53 | {uf:'DF', option: 7, modelValue: '0730000100109', viewValue: '07300001001-09'},
54 | {uf:'ES', option: 8, modelValue: '198712308', viewValue: '19871230-8'},
55 | {uf:'GO', option: 9, modelValue: '109876547', viewValue: '10.987.654-7'},
56 | {uf:'MA', option: 10, modelValue: '120000385', viewValue: '120000385'},
57 | {uf:'MG', option: 11, modelValue: '0623079040081', viewValue: '062.307.904/0081'},
58 | {uf:'MS', option: 12, modelValue: '285730383', viewValue: '285730383'},
59 | {uf:'MT', option: 13, modelValue: '00130000019', viewValue: '0013000001-9'},
60 | {uf:'PA', option: 14, modelValue: '159999995', viewValue: '15-999999-5'},
61 | {uf:'PB', option: 15, modelValue: '060000015', viewValue: '06000001-5'},
62 | {uf:'PE', option: 16, modelValue: '03214184023459', viewValue: '0321418-40', viewValue2: '03.2.141.8402345-9'},
63 | {uf:'PI', option: 17, modelValue: '012345679', viewValue: '012345679'},
64 | {uf:'PR', option: 18, modelValue: '1234567850', viewValue: '123.45678-50'},
65 | {uf:'RJ', option: 19, modelValue: '40732128', viewValue: '40.732.12-8'},
66 | {uf:'RN', option: 20, modelValue: '2004004012', viewValue: '20.040.040-1', viewValue2: '20.0.400.401-2'},
67 | {uf:'RO', option: 21, modelValue: '06012306625217', viewValue: '0601230662521-7'},
68 | {uf:'RR', option: 22, modelValue: '240066281', viewValue: '24006628-1'},
69 | {uf:'RS', option: 23, modelValue: '2243658792', viewValue: '224/3658792'},
70 | {uf:'SC', option: 24, modelValue: '251040852', viewValue: '251.040.852'},
71 | {uf:'SE', option: 25, modelValue: '271234563', viewValue: '27123456-3'},
72 | {uf:'SP', option: 26, modelValue: '110042490114', viewValue: '110.042.490.114'},
73 | {uf:'SP', option: 26, modelValue: 'P011004243002', viewValue: 'P-01100424.3/002'},
74 | {uf:'TO', option: 27, modelValue: '29010227836', viewValue: '29010227836'}
75 | ];
76 |
77 | tests.forEach((test) => {
78 | it(`${test.uf}: should apply a I.E. mask while the user is typping`, function() {
79 | var BS = protractor.Key.BACK_SPACE;
80 |
81 | function getExpectedViewValue(viewValue, i, viewValue2) {
82 | var values = viewValue.split('');
83 | if (i > viewValue.replace(/[^P0-9]/ig,'').length && viewValue2) {
84 | values = viewValue2.split('');
85 | }
86 | var expected = '';
87 | var count = 0;
88 | while (count < i && values.length > 0) {
89 | var c = values.splice(0,1);
90 | expected += c;
91 | count += /[P0-9]/i.test(c);
92 | }
93 | return expected;
94 | }
95 |
96 | var inputIE = element(by.model('inscEst')),
97 | inputUF = element(by.model('state')),
98 | value = element(by.binding('inscEst'));
99 |
100 | inputIE.clear();
101 | var values = test.modelValue.split('');
102 | var viewValue = test.viewValue;
103 | var viewValue2 = test.viewValue2;
104 |
105 | inputUF.all(by.tagName('option')).get(test.option).click();
106 | var i, erroMsg, expected;
107 | for (i = 0; i < values.length; i++) {
108 | inputIE.sendKeys(values[i]);
109 | erroMsg = 'Estado: '+test.uf+'; i: '+i+'; key: '+values[i];
110 | expected = getExpectedViewValue(viewValue, i+1, viewValue2);
111 | expect(inputIE.getAttribute('value')).toEqual(expected, erroMsg);
112 | expect(value.getText()).toEqual(test.modelValue.substr(0,i+1), erroMsg);
113 | }
114 |
115 | for (; i > 0; i--) {
116 | inputIE.sendKeys(BS);
117 | erroMsg = 'Estado: '+test.uf+'; i: '+i+'; key: BS';
118 | expected = getExpectedViewValue(viewValue, i-1, viewValue2);
119 | expect(inputIE.getAttribute('value')).toEqual(expected, erroMsg);
120 | expect(value.getText()).toEqual(test.modelValue.substr(0,i-1), erroMsg);
121 | }
122 | });
123 | });
124 | });
125 |
--------------------------------------------------------------------------------
/src/br/inscricao-estadual/ie.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../br-masks');
4 |
5 | describe('ui-br-ie-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.br'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model value', function() {
26 | var input = TestUtil.compile('', {
27 | model: 'P011004243002'
28 | });
29 | var model = input.controller('ngModel');
30 | expect(model.$viewValue).toBe('P-01100424.3/002');
31 | });
32 |
33 | it('should format with secondary mask', function() {
34 | var input = TestUtil.compile('', {
35 | model: '2004004012'
36 | });
37 | var model = input.controller('ngModel');
38 | expect(model.$viewValue).toBe('20.0.400.401-2');
39 | });
40 |
41 | it('should handle corner cases', angular.mock.inject(function($rootScope) {
42 | var input = TestUtil.compile('');
43 | var model = input.controller('ngModel');
44 |
45 | var tests = [
46 | {modelValue: '', viewValue: ''},
47 | {modelValue: null, viewValue: null},
48 | {}, //tests undefined values
49 | ];
50 |
51 | tests.forEach(function(test) {
52 | $rootScope.model = test.modelValue;
53 | $rootScope.$digest();
54 | expect(model.$viewValue).toBe(test.viewValue);
55 | });
56 | }));
57 |
58 | it('should not format when state is invalid', function() {
59 | var input = TestUtil.compile('', {
60 | model: '0623079040081'
61 | });
62 | var model = input.controller('ngModel');
63 | expect(model.$viewValue).toBe('0623079040081');
64 | input.val('0623079040082').triggerHandler('input');
65 | expect(model.$viewValue).toBe('0623079040082');
66 |
67 | var input2 = TestUtil.compile('', {
68 | model: '0623079040081'
69 | });
70 | var model2 = input.controller('ngModel');
71 | expect(model2.$viewValue).toBe('0623079040081');
72 | input2.val('0623079040082').triggerHandler('input');
73 | expect(model2.$viewValue).toBe('0623079040082');
74 | });
75 |
76 | it('should ignore non digits', function() {
77 | var input = TestUtil.compile('');
78 | var model = input.controller('ngModel');
79 |
80 | var tests = [
81 | {value:'@', viewValue:'', modelValue:''},
82 | {value:'0.', viewValue:'0', modelValue:'0'},
83 | {value:'062&', viewValue:'062', modelValue:'062'},
84 | {value:'062&30!7', viewValue:'062.307', modelValue:'062307'},
85 | {value:'062&3079%04', viewValue:'062.307.904', modelValue:'062307904'},
86 | {value:'062&30*79040', viewValue:'062.307.904/0', modelValue:'0623079040'},
87 | {value:'062&30*79040081', viewValue:'062.307.904/0081', modelValue:'0623079040081'},
88 | ];
89 |
90 | tests.forEach(function(test) {
91 | input.val(test.value).triggerHandler('input');
92 | expect(model.$viewValue).toBe(test.viewValue);
93 | expect(model.$modelValue).toBe(test.modelValue);
94 | });
95 | });
96 | });
97 |
--------------------------------------------------------------------------------
/src/br/nfe/nfe.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NF-e Spec
6 |
7 |
8 |
9 |
14 |
20 |
21 |
22 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/br/nfe/nfe.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 |
5 | var maskFactory = require('../../helpers/mask-factory');
6 |
7 | var nfeAccessKeyMask = new StringMask('0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000');
8 |
9 | module.exports = maskFactory({
10 | clearValue: function(rawValue) {
11 | return rawValue.replace(/[^0-9]/g, '').slice(0, 44);
12 | },
13 | format: function(cleanValue) {
14 | return (nfeAccessKeyMask.apply(cleanValue) || '').replace(/[^0-9]$/, '');
15 | },
16 | validations: {
17 | nfeAccessKey: function(value) {
18 | return value.length === 44;
19 | }
20 | }
21 | });
22 |
--------------------------------------------------------------------------------
/src/br/nfe/nfe.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 |
5 | describe('uiNfeAccessKeyMask', function() {
6 | it('should load the demo page', function() {
7 | browser.get('/src/br/nfe/nfe.html');
8 | expect(browser.getTitle()).toEqual('NF-e Spec');
9 | });
10 |
11 | it('should format a NF-e access key', function() {
12 | var nfeAccessKeyFormatter = new StringMask('0000 0000 0000 0000 0000' +
13 | ' 0000 0000 0000 0000 0000 0000'),
14 | inputKeysToSend = '34958723405162304548623240917012593348590495',
15 | formatedNfeAccessKeyAsString, numberToFormat = '';
16 |
17 | var input = element(by.model('accessKeyField'));
18 |
19 | var i;
20 | for (i = 0; i < 44; i++) {
21 | var key = inputKeysToSend.charAt(i);
22 | input.sendKeys(key);
23 | numberToFormat += key;
24 | formatedNfeAccessKeyAsString = nfeAccessKeyFormatter.apply(numberToFormat).replace(/[^0-9]$/,'');
25 | expect(input.getAttribute('value')).toEqual(formatedNfeAccessKeyAsString);
26 | }
27 |
28 | for (i = 43; i >= 0; i--) {
29 | if (i % 4 === 0) {
30 | input.sendKeys(protractor.Key.BACK_SPACE);
31 | }
32 |
33 | input.sendKeys(protractor.Key.BACK_SPACE);
34 | numberToFormat = numberToFormat.slice(0, -1);
35 | if (numberToFormat) {
36 | formatedNfeAccessKeyAsString = nfeAccessKeyFormatter.apply(numberToFormat).replace(/[^0-9]$/,'');
37 | expect(input.getAttribute('value')).toEqual(formatedNfeAccessKeyAsString);
38 | }
39 | }
40 | });
41 |
42 | it('should be valid if the model is a valid time', function() {
43 | var inputKeysToSend = '23304920235802085168523045823045892349519349';
44 |
45 | var input = element(by.model('accessKeyField')),
46 | valid = element(by.binding('form.accessKeyField.$error'));
47 |
48 | var i;
49 | for (i = 0; i < 43; i++) {
50 | input.sendKeys(inputKeysToSend.charAt(i));
51 | expect(valid.getText()).toEqual('{ "nfeAccessKey": true }');
52 | }
53 |
54 | input.sendKeys(inputKeysToSend.charAt(5));
55 | expect(valid.getText()).toEqual('{}');
56 |
57 | for (i = 43; i > 0; i--) {
58 | if (i % 4 === 0) {
59 | input.sendKeys(protractor.Key.BACK_SPACE);
60 | }
61 |
62 | input.sendKeys(protractor.Key.BACK_SPACE);
63 | expect(valid.getText()).toEqual('{ "nfeAccessKey": true }');
64 | }
65 |
66 | input.sendKeys(protractor.Key.BACK_SPACE);
67 | expect(valid.getText()).toEqual('{}');
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/src/br/nfe/nfe.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../br-masks');
4 |
5 | describe('ui-nfe-access-key-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.br'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: '34958723405162304548623240917012593348590495'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('3495 8723 4051 6230 4548 6232 4091 7012 5933 4859 0495');
32 | });
33 |
34 | it('should ignore non digits', function() {
35 | var input = TestUtil.compile('');
37 | var model = input.controller('ngModel');
38 |
39 | var tests = [
40 | {value:'@', viewValue:'', modelValue:''},
41 | {value:'2-', viewValue:'2', modelValue:'2'},
42 | {value:'23a', viewValue:'23', modelValue:'23'},
43 | {value:'23_34', viewValue:'2334', modelValue:'2334'},
44 | {value:'23346!324', viewValue:'2334 6324', modelValue:'23346324'},
45 | {value:'23346!324sdfg9870', viewValue:'2334 6324 9870', modelValue:'233463249870'},
46 | ];
47 |
48 | tests.forEach(function(test) {
49 | input.val(test.value).triggerHandler('input');
50 | expect(model.$viewValue).toBe(test.viewValue);
51 | expect(model.$modelValue).toBe(test.modelValue);
52 | });
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/src/br/numero-beneficio/numero-beneficio.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Número Benefício Spec
6 |
7 |
8 |
9 |
15 |
16 |
17 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/br/numero-beneficio/numero-beneficio.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 |
5 | var maskFactory = require('../../helpers/mask-factory');
6 |
7 | var numeroBeneficioPattern = new StringMask('###.###.###-#', {reverse: true});
8 |
9 | module.exports = maskFactory({
10 | clearValue: function(rawValue) {
11 | return rawValue.replace(/[^\d]/g, '').slice(0, 10);
12 | },
13 | format: function(cleanValue) {
14 | return (numeroBeneficioPattern.apply(cleanValue) || '').trim().replace(/[^0-9]$/, '');
15 | },
16 | validations: {}
17 | });
18 |
--------------------------------------------------------------------------------
/src/br/numero-beneficio/numero-beneficio.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiBrNumeroBeneficioMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/br/numero-beneficio/numero-beneficio.html');
6 | expect(browser.getTitle()).toEqual('Número Benefício Spec');
7 | });
8 |
9 | it('should apply a Número Benefício mask while the user is typping:', function() {
10 | var BS = protractor.Key.BACK_SPACE;
11 |
12 | var tests = [
13 | {key:'-', viewValue:'', modelValue: ''},
14 | {key:'3', viewValue:'3', modelValue: '3'},
15 | {key:'5', viewValue:'3-5', modelValue: '35'},
16 | {key:'2', viewValue:'35-2', modelValue: '352'},
17 | {key:'4', viewValue:'352-4', modelValue: '3524'},
18 | {key:'4', viewValue:'3.524-4', modelValue: '35244'},
19 | {key:'4', viewValue:'35.244-4', modelValue: '352444'},
20 | {key:'5', viewValue:'352.444-5', modelValue: '3524445'},
21 | {key:'7', viewValue:'3.524.445-7', modelValue: '35244457'},
22 | {key:'6', viewValue:'35.244.457-6', modelValue: '352444576'},
23 | {key:'4', viewValue:'352.444.576-4', modelValue: '3524445764'},
24 | {key:'0', viewValue:'352.444.576-4', modelValue: '3524445764'},
25 | {key:BS, viewValue:'35.244.457-6', modelValue: '352444576'},
26 | {key:BS, viewValue:'3.524.445-7', modelValue: '35244457'},
27 | {key:BS, viewValue:'352.444-5', modelValue: '3524445'},
28 | {key:BS, viewValue:'35.244-4', modelValue: '352444'},
29 | {key:BS, viewValue:'3.524-4', modelValue: '35244'},
30 | {key:BS, viewValue:'352-4', modelValue: '3524'},
31 | {key:BS, viewValue:'35-2', modelValue: '352'},
32 | {key:BS, viewValue:'3-5', modelValue: '35'},
33 | {key:BS, viewValue:'3', modelValue: '3'},
34 | ];
35 |
36 | var input = element(by.model('fieldNumeroBeneficio')),
37 | value = element(by.binding('fieldNumeroBeneficio'));
38 |
39 | for (var i = 0; i < tests.length; i++) {
40 | input.sendKeys(tests[i].key);
41 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
42 | expect(value.getText()).toEqual(tests[i].modelValue);
43 | }
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/src/br/numero-beneficio/numero-beneficio.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../br-masks');
4 |
5 | describe('ui-br-numero-beneficio-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.br'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: '3524445764'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('352.444.576-4');
32 | });
33 |
34 | it('should handle corner cases', angular.mock.inject(function($rootScope) {
35 | var input = TestUtil.compile('');
36 | var model = input.controller('ngModel');
37 |
38 | var tests = [
39 | {modelValue: '', viewValue: ''},
40 | {modelValue: '0', viewValue: '0'},
41 | {modelValue: null, viewValue: null},
42 | {}, //tests undefined values
43 | ];
44 |
45 | tests.forEach(function(test) {
46 | $rootScope.model = test.modelValue;
47 | $rootScope.$digest();
48 | expect(model.$viewValue).toBe(test.viewValue);
49 | });
50 | }));
51 |
52 | it('should ignore non digits', function() {
53 | var input = TestUtil.compile('');
54 | var model = input.controller('ngModel');
55 |
56 | var tests = [
57 | {value:'@', viewValue:'', modelValue:''},
58 | {value:'3!', viewValue:'3', modelValue:'3'},
59 | {value:'35$2', viewValue:'35-2', modelValue:'352'},
60 | {value:'35$24%4.', viewValue:'3.524-4', modelValue:'35244'},
61 | {value:'35$24%445&', viewValue:'352.444-5', modelValue:'3524445'},
62 | {value:'35$24%445&76', viewValue:'35.244.457-6', modelValue:'352444576'},
63 | {value:'35$24%445&7640', viewValue:'352.444.576-4', modelValue:'3524445764'},
64 | ];
65 |
66 | tests.forEach(function(test) {
67 | input.val(test.value).triggerHandler('input');
68 | expect(model.$viewValue).toBe(test.viewValue);
69 | expect(model.$modelValue).toBe(test.modelValue);
70 | });
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/src/br/phone/br-phone.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BR Phone Number Spec
6 |
7 |
8 |
9 |
15 |
16 |
17 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/br/phone/br-phone.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 |
5 | var maskFactory = require('../../helpers/mask-factory');
6 |
7 | var phoneMask8D = {
8 | countryCode : new StringMask('+00 (00) 0000-0000'), //with country code
9 | areaCode : new StringMask('(00) 0000-0000'), //with area code
10 | simple : new StringMask('0000-0000') //without area code
11 | }, phoneMask9D = {
12 | countryCode : new StringMask('+00 (00) 00000-0000'), //with country code
13 | areaCode : new StringMask('(00) 00000-0000'), //with area code
14 | simple : new StringMask('00000-0000') //without area code
15 | }, phoneMask0800 = {
16 | countryCode : null, //N/A
17 | areaCode : null, //N/A
18 | simple : new StringMask('0000-000-0000') //N/A, so it's "simple"
19 | };
20 |
21 | var brPhoneMaskOptions = {
22 | 'countryCode': {sliceSize: 13, min: 12, max: 13},
23 | 'areaCode': {sliceSize: 11, min: 10, max: 11},
24 | 'simple': {sliceSize: 9, min: 8, max: 9},
25 | 'all': {sliceSize: 13, min: 8, max: 13}
26 | };
27 |
28 | function findOption(attrs) {
29 | var brPhoneMaskOption = brPhoneMaskOptions.all;
30 |
31 | if (attrs && attrs.uiBrPhoneNumberMask) {
32 | var maskOption = attrs.uiBrPhoneNumberMask;
33 | angular.forEach(brPhoneMaskOptions, function(value, key) {
34 | if (key === maskOption) {
35 | brPhoneMaskOption = value;
36 | return;
37 | }
38 | });
39 | }
40 |
41 | return brPhoneMaskOption;
42 | }
43 |
44 | module.exports = maskFactory({
45 | clearValue: function(rawValue, attrs) {
46 | var brPhoneMaskOption = findOption(attrs);
47 | return rawValue.toString().replace(/[^0-9]/g, '').slice(0, brPhoneMaskOption.sliceSize);
48 | },
49 | format: function(cleanValue) {
50 | var formattedValue;
51 |
52 | if (cleanValue.indexOf('0800') === 0) {
53 | formattedValue = phoneMask0800.simple.apply(cleanValue);
54 | } else if (cleanValue.length < 9) {
55 | formattedValue = phoneMask8D.simple.apply(cleanValue) || '';
56 | } else if (cleanValue.length < 10) {
57 | formattedValue = phoneMask9D.simple.apply(cleanValue);
58 | } else if (cleanValue.length < 11) {
59 | formattedValue = phoneMask8D.areaCode.apply(cleanValue);
60 | } else if (cleanValue.length < 12) {
61 | formattedValue = phoneMask9D.areaCode.apply(cleanValue);
62 | } else if (cleanValue.length < 13) {
63 | formattedValue = phoneMask8D.countryCode.apply(cleanValue);
64 | } else {
65 | formattedValue = phoneMask9D.countryCode.apply(cleanValue);
66 | }
67 |
68 | return formattedValue.trim().replace(/[^0-9]$/, '');
69 | },
70 | getModelValue: function(formattedValue, originalModelType) {
71 | var cleanValue = this.clearValue(formattedValue);
72 | return originalModelType === 'number' ? parseInt(cleanValue) : cleanValue;
73 | },
74 | validations: {
75 | brPhoneNumber: function(value, view, attrs) {
76 | var brPhoneMaskOption = findOption(attrs);
77 | var valueLength = value && value.toString().length;
78 |
79 | //8- 8D without AC
80 | //9- 9D without AC
81 | //10- 8D with AC
82 | //11- 9D with AC and 0800
83 | //12- 8D with AC plus CC
84 | //13- 9D with AC plus CC
85 | return valueLength >= brPhoneMaskOption.min && valueLength <= brPhoneMaskOption.max;
86 | }
87 | }
88 | });
89 |
--------------------------------------------------------------------------------
/src/ch/ch-masks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var m = angular.module('ui.utils.masks.ch', [])
4 | .directive('uiChPhoneNumberMask', require('./phone/ch-phone'));
5 |
6 | module.exports = m.name;
7 |
--------------------------------------------------------------------------------
/src/ch/phone/ch-phone.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CH Phone Number Spec
6 |
7 |
8 |
9 |
15 |
16 |
17 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/ch/phone/ch-phone.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 |
5 | var maskFactory = require('../../helpers/mask-factory');
6 |
7 | var phoneMask = new StringMask('+00 00 000 00 00');
8 |
9 | module.exports = maskFactory({
10 | clearValue: function(rawValue) {
11 | return rawValue.toString().replace(/[^0-9]/g, '').slice(0, 11);
12 | },
13 | format: function(cleanValue) {
14 | var formatedValue;
15 |
16 | formatedValue = phoneMask.apply(cleanValue) || '';
17 |
18 | return formatedValue.trim().replace(/[^0-9]$/, '');
19 | },
20 | validations: {
21 | chPhoneNumber: function(value) {
22 | var valueLength = value && value.toString().length;
23 | return valueLength === 11;
24 | }
25 | }
26 | });
27 |
--------------------------------------------------------------------------------
/src/ch/phone/ch-phone.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiChPhoneNumberMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/ch/phone/ch-phone.html');
6 | expect(browser.getTitle()).toEqual('CH Phone Number Spec');
7 | });
8 |
9 | it('should apply a phone number mask while the user is typping:', function() {
10 | var BS = protractor.Key.BACK_SPACE;
11 |
12 | var tests = [
13 | {key:'1', viewValue:'+1', modelValue:'1'},
14 | {key:'2', viewValue:'+12', modelValue:'12'},
15 | {key:'3', viewValue:'+12 3', modelValue:'123'},
16 | {key:'4', viewValue:'+12 34', modelValue:'1234'},
17 | {key:'5', viewValue:'+12 34 5', modelValue:'12345'},
18 | {key:'6', viewValue:'+12 34 56', modelValue:'123456'},
19 | {key:'7', viewValue:'+12 34 567', modelValue:'1234567'},
20 | {key:'8', viewValue:'+12 34 567 8', modelValue:'12345678'},
21 | {key:'9', viewValue:'+12 34 567 89', modelValue:'123456789'},
22 | {key:'0', viewValue:'+12 34 567 89 0', modelValue:'1234567890'},
23 | {key:'1', viewValue:'+12 34 567 89 01', modelValue:'12345678901'},
24 | {key:'2', viewValue:'+12 34 567 89 01', modelValue:'12345678901'},
25 | {key:BS, viewValue:'+12 34 567 89 0', modelValue:'1234567890'},
26 | {key:BS, viewValue:'+12 34 567 89 ', modelValue:'123456789'},
27 | {key:BS, viewValue:'+12 34 567 89', modelValue:'123456789'},
28 | {key:BS, viewValue:'+12 34 567 8', modelValue:'12345678'},
29 | {key:BS, viewValue:'+12 34 567 ', modelValue:'1234567'},
30 | {key:BS, viewValue:'+12 34 567', modelValue:'1234567'},
31 | {key:BS, viewValue:'+12 34 56', modelValue:'123456'},
32 | {key:BS, viewValue:'+12 34 5', modelValue:'12345'},
33 | {key:BS, viewValue:'+12 34 ', modelValue:'1234'},
34 | {key:BS, viewValue:'+12 34', modelValue:'1234'},
35 | {key:BS, viewValue:'+12 3', modelValue:'123'},
36 | {key:BS, viewValue:'+12 ', modelValue:'12'},
37 | {key:BS, viewValue:'+12', modelValue:'12'},
38 | {key:BS, viewValue:'+1', modelValue:'1'},
39 | {key:BS, viewValue:'', modelValue:''},
40 | ];
41 |
42 | var input = element(by.model('phoneNumber')),
43 | value = element(by.exactBinding('phoneNumber'));
44 |
45 | for (var i = 0; i < tests.length; i++) {
46 | input.sendKeys(tests[i].key);
47 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
48 | expect(value.getText()).toEqual(tests[i].modelValue);
49 | }
50 | });
51 |
52 | it('should apply a phone number mask in a model with default value:', function() {
53 | var BS = protractor.Key.BACK_SPACE;
54 |
55 | var tests = [
56 | {key:'1', viewValue:'+1', modelValue:'1'},
57 | {key:'2', viewValue:'+12', modelValue:'12'},
58 | {key:'3', viewValue:'+12 3', modelValue:'123'},
59 | {key:'4', viewValue:'+12 34', modelValue:'1234'},
60 | {key:'5', viewValue:'+12 34 5', modelValue:'12345'},
61 | {key:'6', viewValue:'+12 34 56', modelValue:'123456'},
62 | {key:'7', viewValue:'+12 34 567', modelValue:'1234567'},
63 | {key:BS, viewValue:'+12 34 56', modelValue:'123456'},
64 | {key:BS, viewValue:'+12 34 5', modelValue:'12345'},
65 | {key:BS, viewValue:'+12 34 ', modelValue:'1234'},
66 | {key:BS, viewValue:'+12 34', modelValue:'1234'},
67 | {key:BS, viewValue:'+12 3', modelValue:'123'},
68 | {key:BS, viewValue:'+12 ', modelValue:'12'},
69 | {key:BS, viewValue:'+12', modelValue:'12'},
70 | {key:BS, viewValue:'+1', modelValue:'1'},
71 | {key:BS, viewValue:'', modelValue:''},
72 | ];
73 |
74 | var input = element(by.model('initializedPhoneNumber')),
75 | value = element(by.exactBinding('initializedPhoneNumber'));
76 |
77 | expect(input.getAttribute('value')).toEqual('+41 79 000 00 00');
78 | input.clear();
79 |
80 | for (var i = 0; i < tests.length; i++) {
81 | input.sendKeys(tests[i].key);
82 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
83 | expect(value.getText()).toEqual(tests[i].modelValue);
84 | }
85 | });
86 | });
87 |
--------------------------------------------------------------------------------
/src/ch/phone/ch-phone.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../ch-masks');
4 |
5 | describe('uiChPhoneNumberMask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.ch'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: '41790000000'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('+41 79 000 00 00');
32 | });
33 |
34 | it('should ignore non digits', function() {
35 | var input = TestUtil.compile('');
36 | var model = input.controller('ngModel');
37 |
38 | var tests = [
39 | {value:'@', viewValue:'', modelValue:''},
40 | {value:'2-', viewValue:'+2', modelValue:'2'},
41 | {value:'23a', viewValue:'+23', modelValue:'23'},
42 | {value:'23_34', viewValue:'+23 34', modelValue:'2334'},
43 | {value:'23346!', viewValue:'+23 34 6', modelValue:'23346'},
44 | {value:'23346!324', viewValue:'+23 34 632 4', modelValue:'23346324'},
45 | ];
46 |
47 | tests.forEach(function(test) {
48 | input.val(test.value).triggerHandler('input');
49 | expect(model.$viewValue).toBe(test.viewValue);
50 | expect(model.$modelValue).toBe(test.modelValue);
51 | });
52 | });
53 |
54 | it('should validate a phone number', function() {
55 | var input = TestUtil.compile('', {
56 | model: '417900'
57 | });
58 |
59 | var model = input.controller('ngModel');
60 | expect(model.$error.chPhoneNumber).toBe(true);
61 | input.val('41790000000').triggerHandler('input');
62 | expect(model.$error.chPhoneNumber).toBeUndefined();
63 | });
64 |
65 | it('should use the type of the model value (if initialized)', function() {
66 | var input = TestUtil.compile('', {
67 | model: '41790000000'
68 | });
69 |
70 | var model = input.controller('ngModel');
71 | expect(model.$viewValue).toBe('+41 79 000 00 00');
72 | expect(model.$modelValue).toBe('41790000000');
73 | input.val('41619618686').triggerHandler('input');
74 | expect(model.$viewValue).toBe('+41 61 961 86 86');
75 | expect(model.$modelValue).toBe('41619618686');
76 | });
77 | });
78 |
--------------------------------------------------------------------------------
/src/fr/fr-masks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var m = angular.module('ui.utils.masks.fr', [])
4 | .directive('uiFrPhoneNumberMask', require('./phone/fr-phone'));
5 |
6 | module.exports = m.name;
7 |
--------------------------------------------------------------------------------
/src/fr/phone/fr-phone.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FR Phone Number Spec
6 |
7 |
8 |
9 |
15 |
16 |
17 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/fr/phone/fr-phone.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var maskFactory = require('../../helpers/mask-factory');
5 |
6 | var phoneMaskFR = new StringMask('00 00 00 00 00');
7 |
8 | module.exports = maskFactory({
9 | clearValue: function(rawValue) {
10 | return rawValue.toString().replace(/[^0-9]/g, '').slice(0, 10);
11 | },
12 | format: function(cleanValue) {
13 | var formattedValue;
14 |
15 | formattedValue = phoneMaskFR.apply(cleanValue) || '';
16 |
17 | return formattedValue.trim().replace(/[^0-9]$/, '');
18 | },
19 | validations: {
20 | frPhoneNumber: function(value) {
21 | var valueLength = value && value.toString().length;
22 | return valueLength === 10;
23 | }
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/src/fr/phone/fr-phone.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiFrPhoneNumberMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/fr/phone/fr-phone.html');
6 | expect(browser.getTitle()).toEqual('FR Phone Number Spec');
7 | });
8 |
9 | var runTests = function(input, value) {
10 | var BS = protractor.Key.BACK_SPACE;
11 | var tests = [
12 | {key:'1', viewValue:'1', modelValue:'1'},
13 | {key:'2', viewValue:'12', modelValue:'12'},
14 | {key:'3', viewValue:'12 3', modelValue:'123'},
15 | {key:'4', viewValue:'12 34', modelValue:'1234'},
16 | {key:'5', viewValue:'12 34 5', modelValue:'12345'},
17 | {key:'6', viewValue:'12 34 56', modelValue:'123456'},
18 | {key:'7', viewValue:'12 34 56 7', modelValue:'1234567'},
19 | {key:'8', viewValue:'12 34 56 78', modelValue:'12345678'},
20 | {key:'9', viewValue:'12 34 56 78 9', modelValue:'123456789'},
21 | {key:'0', viewValue:'12 34 56 78 90', modelValue:'1234567890'},
22 | {key:'1', viewValue:'12 34 56 78 90', modelValue:'1234567890'},
23 | {key:BS, viewValue:'12 34 56 78 9', modelValue:'123456789'},
24 | {key:BS, viewValue:'12 34 56 78 ', modelValue:'12345678'},
25 | {key:BS, viewValue:'12 34 56 78', modelValue:'12345678'},
26 | {key:BS, viewValue:'12 34 56 7', modelValue:'1234567'},
27 | {key:BS, viewValue:'12 34 56 ', modelValue:'123456'},
28 | {key:BS, viewValue:'12 34 56', modelValue:'123456'},
29 | {key:BS, viewValue:'12 34 5', modelValue:'12345'},
30 | {key:BS, viewValue:'12 34 ', modelValue:'1234'},
31 | {key:BS, viewValue:'12 34', modelValue:'1234'},
32 | {key:BS, viewValue:'12 3', modelValue:'123'},
33 | {key:BS, viewValue:'12 ', modelValue:'12'},
34 | {key:BS, viewValue:'12', modelValue:'12'},
35 | {key:BS, viewValue:'1', modelValue:'1'},
36 | {key:BS, viewValue:'', modelValue:''},
37 | ];
38 |
39 | for (var i = 0; i < tests.length; i++) {
40 | input.sendKeys(tests[i].key);
41 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
42 | expect(value.getText()).toEqual(tests[i].modelValue);
43 | }
44 | };
45 |
46 | it('should apply a phone number mask while the user is typing:', function() {
47 | var input = element(by.id('fr-phone-input')),
48 | value = element(by.id('fr-phone-value'));
49 |
50 | runTests(input, value);
51 | });
52 |
53 | it('should apply a phone number mask in a model with default value:', function() {
54 | var input = element(by.id('init-fr-phone-input')),
55 | value = element(by.id('init-fr-phone-value'));
56 |
57 | expect(input.getAttribute('value')).toEqual('31 33 53 67 67');
58 | input.clear();
59 |
60 | runTests(input, value);
61 | });
62 | });
--------------------------------------------------------------------------------
/src/fr/phone/fr-phone.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../fr-masks');
4 |
5 | describe('uiFrPhoneNumberMask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.fr'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values (4+10D)', function() {
26 | var input = TestUtil.compile('', {
27 | model: '3011201034'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('30 11 20 10 34');
32 | });
33 |
34 | it('should ignore non digits', function() {
35 | var input = TestUtil.compile('');
36 | var model = input.controller('ngModel');
37 |
38 | var tests = [
39 | {value:'@', viewValue:'', modelValue:''},
40 | {value:'2-', viewValue:'2', modelValue:'2'},
41 | {value:'23a', viewValue:'23', modelValue:'23'},
42 | {value:'23_34', viewValue:'23 34', modelValue:'2334'},
43 | {value:'23346!', viewValue:'23 34 6', modelValue:'23346'},
44 | {value:'23346!324', viewValue:'23 34 63 24', modelValue:'23346324'},
45 | ];
46 |
47 | tests.forEach(function(test) {
48 | input.val(test.value).triggerHandler('input');
49 | expect(model.$viewValue).toBe(test.viewValue);
50 | expect(model.$modelValue).toBe(test.modelValue);
51 | });
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/src/global/credit-card/credit-card.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Credit Card Spec
6 |
7 |
8 |
9 |
15 |
16 |
17 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/global/credit-card/credit-card.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var maskFactory = require('../../helpers/mask-factory');
5 |
6 | var ccSize = 16;
7 |
8 | var ccMask = new StringMask('0000 0000 0000 0000');
9 |
10 | module.exports = maskFactory({
11 | clearValue: function(rawValue) {
12 | return rawValue.toString().replace(/[^0-9]/g, '').slice(0, ccSize);
13 | },
14 | format: function(cleanValue) {
15 | var formatedValue;
16 |
17 | formatedValue = ccMask.apply(cleanValue) || '';
18 |
19 | return formatedValue.trim().replace(/[^0-9]$/, '');
20 | },
21 | validations: {
22 | creditCard: function(value) {
23 | var valueLength = value && value.toString().length;
24 | return valueLength === ccSize;
25 | }
26 | }
27 | });
28 |
--------------------------------------------------------------------------------
/src/global/credit-card/credit-card.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiCreditCardMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/global/credit-card/credit-card.html');
6 | expect(browser.getTitle()).toEqual('Credit Card Spec');
7 | });
8 |
9 | it('should apply a credit card number mask while the user is typping:', function() {
10 | var BS = protractor.Key.BACK_SPACE;
11 |
12 | var tests = [
13 | {key:'1', viewValue:'1', modelValue:'1'},
14 | {key:'2', viewValue:'12', modelValue:'12'},
15 | {key:'3', viewValue:'123', modelValue:'123'},
16 | {key:'4', viewValue:'1234', modelValue:'1234'},
17 | {key:'5', viewValue:'1234 5', modelValue:'12345'},
18 | {key:'6', viewValue:'1234 56', modelValue:'123456'},
19 | {key:'7', viewValue:'1234 567', modelValue:'1234567'},
20 | {key:'8', viewValue:'1234 5678', modelValue:'12345678'},
21 | {key:'9', viewValue:'1234 5678 9', modelValue:'123456789'},
22 | {key:'0', viewValue:'1234 5678 90', modelValue:'1234567890'},
23 | {key:'1', viewValue:'1234 5678 901', modelValue:'12345678901'},
24 | {key:'2', viewValue:'1234 5678 9012', modelValue:'123456789012'},
25 | {key:'3', viewValue:'1234 5678 9012 3', modelValue:'1234567890123'},
26 | {key:'4', viewValue:'1234 5678 9012 34', modelValue:'12345678901234'},
27 | {key:'5', viewValue:'1234 5678 9012 345', modelValue:'123456789012345'},
28 | {key:'6', viewValue:'1234 5678 9012 3456', modelValue:'1234567890123456'},
29 | {key:'7', viewValue:'1234 5678 9012 3456', modelValue:'1234567890123456'},
30 | {key:BS, viewValue:'1234 5678 9012 345', modelValue:'123456789012345'},
31 | {key:BS, viewValue:'1234 5678 9012 34', modelValue:'12345678901234'},
32 | {key:BS, viewValue:'1234 5678 9012 3', modelValue:'1234567890123'},
33 | {key:BS, viewValue:'1234 5678 9012 ', modelValue:'123456789012'},
34 | {key:BS, viewValue:'1234 5678 9012', modelValue:'123456789012'},
35 | {key:BS, viewValue:'1234 5678 901', modelValue:'12345678901'},
36 | {key:BS, viewValue:'1234 5678 90', modelValue:'1234567890'},
37 | {key:BS, viewValue:'1234 5678 9', modelValue:'123456789'},
38 | {key:BS, viewValue:'1234 5678 ', modelValue:'12345678'},
39 | {key:BS, viewValue:'1234 5678', modelValue:'12345678'},
40 | {key:BS, viewValue:'1234 567', modelValue:'1234567'},
41 | {key:BS, viewValue:'1234 56', modelValue:'123456'},
42 | {key:BS, viewValue:'1234 5', modelValue:'12345'},
43 | {key:BS, viewValue:'1234 ', modelValue:'1234'},
44 | {key:BS, viewValue:'1234', modelValue:'1234'},
45 | {key:BS, viewValue:'123', modelValue:'123'},
46 | {key:BS, viewValue:'12', modelValue:'12'},
47 | {key:BS, viewValue:'1', modelValue:'1'},
48 | {key:BS, viewValue:'', modelValue:''},
49 | ];
50 |
51 | var input = element(by.model('creditCard')),
52 | value = element(by.exactBinding('creditCard'));
53 |
54 | for (var i = 0; i < tests.length; i++) {
55 | input.sendKeys(tests[i].key);
56 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
57 | expect(value.getText()).toEqual(tests[i].modelValue);
58 | }
59 | });
60 |
61 | it('should apply a credit card number mask in a model with default value:', function() {
62 | var BS = protractor.Key.BACK_SPACE;
63 |
64 | var tests = [
65 | {key:'1', viewValue:'1', modelValue:'1'},
66 | {key:'2', viewValue:'12', modelValue:'12'},
67 | {key:'3', viewValue:'123', modelValue:'123'},
68 | {key:'4', viewValue:'1234', modelValue:'1234'},
69 | {key:'5', viewValue:'1234 5', modelValue:'12345'},
70 | {key:'6', viewValue:'1234 56', modelValue:'123456'},
71 | {key:'7', viewValue:'1234 567', modelValue:'1234567'},
72 | {key:BS, viewValue:'1234 56', modelValue:'123456'},
73 | {key:BS, viewValue:'1234 5', modelValue:'12345'},
74 | {key:BS, viewValue:'1234 ', modelValue:'1234'},
75 | {key:BS, viewValue:'1234', modelValue:'1234'},
76 | {key:BS, viewValue:'123', modelValue:'123'},
77 | {key:BS, viewValue:'12', modelValue:'12'},
78 | {key:BS, viewValue:'1', modelValue:'1'},
79 | {key:BS, viewValue:'', modelValue:''},
80 | ];
81 |
82 | var input = element(by.model('initializedCC')),
83 | value = element(by.exactBinding('initializedCC'));
84 |
85 | expect(input.getAttribute('value')).toEqual('4242 4242 4242 4242');
86 | input.clear();
87 |
88 | for (var i = 0; i < tests.length; i++) {
89 | input.sendKeys(tests[i].key);
90 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
91 | expect(value.getText()).toEqual(tests[i].modelValue);
92 | }
93 | });
94 | });
--------------------------------------------------------------------------------
/src/global/credit-card/credit-card.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../global-masks');
4 |
5 | describe('uiCreditCardMask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.global'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: '4242424242424242'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('4242 4242 4242 4242');
32 | });
33 |
34 | it('should ignore non digits', function() {
35 | var input = TestUtil.compile('');
36 | var model = input.controller('ngModel');
37 |
38 | var tests = [
39 | {value:'@', viewValue:'', modelValue:''},
40 | {value:'2-', viewValue:'2', modelValue:'2'},
41 | {value:'23a', viewValue:'23', modelValue:'23'},
42 | {value:'23_34', viewValue:'2334', modelValue:'2334'},
43 | {value:'23346!', viewValue:'2334 6', modelValue:'23346'},
44 | {value:'23346!324', viewValue:'2334 6324', modelValue:'23346324'},
45 | {value:'23346!324a32', viewValue:'2334 6324 32', modelValue:'2334632432'},
46 | ];
47 |
48 | tests.forEach(function(test) {
49 | input.val(test.value).triggerHandler('input');
50 | expect(model.$viewValue).toBe(test.viewValue);
51 | expect(model.$modelValue).toBe(test.modelValue);
52 | });
53 | });
54 |
55 | it('should validate a credit card number', function() {
56 | var input = TestUtil.compile('', {
57 | model: '417900'
58 | });
59 |
60 | var model = input.controller('ngModel');
61 | expect(model.$error.creditCard).toBe(true);
62 | input.val('1111222244445555').triggerHandler('input');
63 | expect(model.$error.creditCard).toBeUndefined();
64 | });
65 |
66 | it('should use the type of the model value (if initialized)', function() {
67 | var input = TestUtil.compile('', {
68 | model: '7777333300008888'
69 | });
70 |
71 | var model = input.controller('ngModel');
72 | expect(model.$viewValue).toBe('7777 3333 0000 8888');
73 | expect(model.$modelValue).toBe('7777333300008888');
74 | input.val('1234098712340987').triggerHandler('input');
75 | expect(model.$viewValue).toBe('1234 0987 1234 0987');
76 | expect(model.$modelValue).toBe('1234098712340987');
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/src/global/date/date-pt-br.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Date Spec
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/global/date/date.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Date Spec
6 |
7 |
8 |
9 |
16 |
17 |
18 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/global/date/date.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var formatDate = require('date-fns/format');
4 | var parseDate = require('date-fns/parse');
5 | var isValidDate = require('date-fns/isValid');
6 | var StringMask = require('string-mask');
7 |
8 | function isISODateString(date) {
9 | return /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}([-+][0-9]{2}:[0-9]{2}|Z)$/
10 | .test(date.toString());
11 | }
12 |
13 | var dateFormatMapByLocale = {
14 | 'pt-br': 'DD/MM/YYYY',
15 | 'es-ar': 'DD/MM/YYYY',
16 | 'es-mx': 'DD/MM/YYYY',
17 | 'es' : 'DD/MM/YYYY',
18 | 'en-us': 'MM/DD/YYYY',
19 | 'en' : 'MM/DD/YYYY',
20 | 'fr-fr': 'DD/MM/YYYY',
21 | 'fr' : 'DD/MM/YYYY',
22 | 'ru' : 'DD.MM.YYYY'
23 | };
24 |
25 | function DateMaskDirective($locale) {
26 | var dateFormat = dateFormatMapByLocale[$locale.id] || 'YYYY-MM-DD';
27 |
28 | return {
29 | restrict: 'A',
30 | require: 'ngModel',
31 | link: function(scope, element, attrs, ctrl) {
32 | attrs.parse = attrs.parse || 'true';
33 |
34 | dateFormat = attrs.uiDateMask || dateFormat;
35 |
36 | var dateMask = new StringMask(dateFormat.replace(/[YMD]/g,'0'));
37 |
38 | function formatter(value) {
39 | if (ctrl.$isEmpty(value)) {
40 | return null;
41 | }
42 |
43 | var cleanValue = value;
44 | if (typeof value === 'object' || isISODateString(value)) {
45 | cleanValue = formatDate(value, dateFormat);
46 | }
47 |
48 | cleanValue = cleanValue.replace(/[^0-9]/g, '');
49 | var formatedValue = dateMask.apply(cleanValue) || '';
50 |
51 | return formatedValue.trim().replace(/[^0-9]$/, '');
52 | }
53 |
54 | ctrl.$formatters.push(formatter);
55 |
56 | ctrl.$parsers.push(function parser(value) {
57 | if (ctrl.$isEmpty(value)) {
58 | return value;
59 | }
60 |
61 | var formatedValue = formatter(value);
62 |
63 | if (ctrl.$viewValue !== formatedValue) {
64 | ctrl.$setViewValue(formatedValue);
65 | ctrl.$render();
66 | }
67 |
68 | return attrs.parse === 'false'
69 | ? formatedValue
70 | : parseDate(formatedValue, dateFormat, new Date());
71 | });
72 |
73 | ctrl.$validators.date = function validator(modelValue, viewValue) {
74 | if (ctrl.$isEmpty(modelValue)) {
75 | return true;
76 | }
77 |
78 | return isValidDate(parseDate(viewValue, dateFormat, new Date())) && viewValue.length === dateFormat.length;
79 | };
80 | }
81 | };
82 | }
83 | DateMaskDirective.$inject = ['$locale'];
84 |
85 | module.exports = DateMaskDirective;
86 |
--------------------------------------------------------------------------------
/src/global/date/date.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var parseDate = require('date-fns/parse');
5 | var formatDate = require('date-fns/format');
6 |
7 | describe('uiDateMask', function() {
8 | describe('default ("YYYY-MM-DD") mask', function() {
9 | it('should load the demo page', function() {
10 | browser.get('/src/global/date/date.html');
11 | expect(browser.getTitle()).toEqual('Date Spec');
12 | });
13 |
14 | it('should format a date', function() {
15 | var dateFormatter = new StringMask('0000-00-00'),
16 | formatedDateAsString, numberToFormat = '', inputKeysToSend = '19991231';
17 |
18 | var input = element(by.model('dateMask')),
19 | value = element(by.exactBinding('dateMask'));
20 |
21 | var i;
22 | for (i = 0; i < 8; i++) {
23 | var key = inputKeysToSend.charAt(i);
24 | input.sendKeys(key);
25 | numberToFormat += key;
26 | formatedDateAsString = dateFormatter.apply(numberToFormat).replace(/-$/,'');
27 | expect(input.getAttribute('value')).toEqual(formatedDateAsString);
28 | }
29 |
30 | expect(value.evaluate('dateMask.toISOString()')).toEqual(parseDate(formatedDateAsString, 'YYYY-MM-DD', new Date()).toISOString());
31 |
32 | for (i = 7; i >= 0; i--) {
33 | input.sendKeys(protractor.Key.BACK_SPACE);
34 | numberToFormat = numberToFormat.slice(0, -1);
35 | if (numberToFormat) {
36 | formatedDateAsString = dateFormatter.apply(numberToFormat).replace(/-$/, '');
37 | expect(input.getAttribute('value')).toEqual(formatedDateAsString);
38 | }
39 | }
40 | });
41 |
42 | it('should format a model initialized with a date object', function() {
43 | var input = element(by.model('initializedDateMask')),
44 | value = element(by.exactBinding('initializedDateMask'));
45 |
46 | value.evaluate('initializedDateMask.toString()').then((initialValue) => {
47 | var dateValue = formatDate(new Date(initialValue), 'YYYY-MM-DD');
48 | expect(input.getAttribute('value')).toEqual(dateValue);
49 | });
50 | });
51 |
52 | it('should format a model initialized with a ISO string', function() {
53 | var input = element(by.model('initializedWithISOStringDateMask')),
54 | value = element(by.exactBinding('initializedWithISOStringDateMask'));
55 |
56 | value.getText().then((textValue) => {
57 | var dateValue = formatDate(parseDate(textValue, 'YYYY-MM-DD', new Date()), 'YYYY-MM-DD');
58 | expect(input.getAttribute('value')).toEqual(dateValue);
59 | });
60 | });
61 |
62 | it('should be valid if the model is a valid date', function() {
63 | var inputKeysToSend = '12311999';
64 |
65 | var input = element(by.model('dateMask')),
66 | valid = element(by.binding('form.dateMaskInput.$error'));
67 |
68 | var i;
69 | for (i = 0; i < 7; i++) {
70 | input.sendKeys(inputKeysToSend.charAt(i));
71 | expect(valid.getText()).toEqual('{ "date": true }');
72 | }
73 |
74 | input.sendKeys(inputKeysToSend.charAt(7));
75 | expect(valid.getText()).toEqual('{}');
76 |
77 | for (i = 7; i > 0; i--) {
78 | input.sendKeys(protractor.Key.BACK_SPACE);
79 | expect(valid.getText()).toEqual('{ "date": true }');
80 | }
81 |
82 | input.sendKeys(protractor.Key.BACK_SPACE);
83 | expect(valid.getText()).toEqual('{}');
84 | });
85 | });
86 |
87 | describe('pt-br ("DD/MM/YYYY") mask', function() {
88 | it('should load the demo page', function() {
89 | browser.get('/src/global/date/date-pt-br.html');
90 | expect(browser.getTitle()).toEqual('Date Spec');
91 | });
92 |
93 | it('should format a date', function() {
94 | var dateFormatter = new StringMask('00/00/0000'),
95 | formatedDateAsString, numberToFormat = '', inputKeysToSend = '31121999';
96 |
97 | var input = element(by.model('dateMask')),
98 | value = element(by.exactBinding('dateMask'));
99 |
100 | var i;
101 | for (i = 0; i < 8; i++) {
102 | var key = inputKeysToSend.charAt(i);
103 | input.sendKeys(key);
104 | numberToFormat += key;
105 | formatedDateAsString = dateFormatter.apply(numberToFormat).replace(/\/$/,'');
106 | expect(input.getAttribute('value')).toEqual(formatedDateAsString);
107 | }
108 |
109 | expect(value.evaluate('dateMask.toISOString()')).toEqual(parseDate(formatedDateAsString, 'DD/MM/YYYY', new Date()).toISOString());
110 |
111 | for (i = 7; i >= 0; i--) {
112 | input.sendKeys(protractor.Key.BACK_SPACE);
113 | numberToFormat = numberToFormat.slice(0, -1);
114 | if (numberToFormat) {
115 | formatedDateAsString = dateFormatter.apply(numberToFormat).replace(/\/$/,'');
116 | expect(input.getAttribute('value')).toEqual(formatedDateAsString);
117 | }
118 | }
119 | });
120 |
121 | it('should format a model initialized with a date object', function() {
122 | var input = element(by.model('initializedDateMask')),
123 | value = element(by.exactBinding('initializedDateMask'));
124 |
125 | value.evaluate('initializedDateMask.toString()').then((initialValue) => {
126 | var dateValue = formatDate(new Date(initialValue), 'DD/MM/YYYY');
127 | expect(input.getAttribute('value')).toEqual(dateValue);
128 | });
129 | });
130 |
131 | it('should format a model initialized with a ISO string', function() {
132 | var input = element(by.model('initializedWithISOStringDateMask')),
133 | value = element(by.exactBinding('initializedWithISOStringDateMask'));
134 |
135 | value.evaluate('initializedDateMask.toString()').then((initialValue) => {
136 | var dateValue = formatDate(new Date(initialValue), 'DD/MM/YYYY');
137 | expect(input.getAttribute('value')).toEqual(dateValue);
138 | });
139 | });
140 |
141 | it('should be valid if the model is a valid date', function() {
142 | var inputKeysToSend = '31121999';
143 |
144 | var input = element(by.model('dateMask')),
145 | valid = element(by.binding('form.dateMaskInput.$error'));
146 |
147 | var i;
148 | for (i = 0; i < 7; i++) {
149 | input.sendKeys(inputKeysToSend.charAt(i));
150 | expect(valid.getText()).toEqual('{ "date": true }');
151 | }
152 |
153 | input.sendKeys(inputKeysToSend.charAt(7));
154 | expect(valid.getText()).toEqual('{}');
155 |
156 | for (i = 7; i > 0; i--) {
157 | input.sendKeys(protractor.Key.BACK_SPACE);
158 | expect(valid.getText()).toEqual('{ "date": true }');
159 | }
160 |
161 | input.sendKeys(protractor.Key.BACK_SPACE);
162 | expect(valid.getText()).toEqual('{}');
163 | });
164 | });
165 | });
166 |
--------------------------------------------------------------------------------
/src/global/date/date.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../global-masks');
4 |
5 | describe('ui-date-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.global'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var date = new Date('1999-12-31 00:00:00');
27 | var input = TestUtil.compile('', {
28 | model: date
29 | });
30 |
31 | var model = input.controller('ngModel');
32 | expect(model.$viewValue).toBe('12/31/1999');
33 | });
34 |
35 | it('should use specified mask', function() {
36 | var input = TestUtil.compile('', {
37 | model: new Date('1999-12-31 00:00:00')
38 | });
39 |
40 | var model = input.controller('ngModel');
41 | expect(model.$viewValue).toBe('31.12.1999');
42 | });
43 |
44 | it('should ignore non digits', function() {
45 | var input = TestUtil.compile('');
46 | var model = input.controller('ngModel');
47 |
48 | var tests = [
49 | {value:'@', viewValue:''},
50 | {value:'1-', viewValue:'1'},
51 | {value:'1999a', viewValue:'1999'},
52 | {value:'1999_12', viewValue:'1999-12'},
53 | {value:'1999123!', viewValue:'1999-12-3'},
54 | {value:'199912*31', viewValue:'1999-12-31'},
55 | ];
56 |
57 | tests.forEach(function(test) {
58 | input.val(test.value).triggerHandler('input');
59 | expect(model.$viewValue).toBe(test.viewValue);
60 | });
61 | });
62 |
63 | it('should parse input', angular.mock.inject(function($rootScope) {
64 | var input = TestUtil.compile('');
65 |
66 | input.val('1999-12-31').triggerHandler('input');
67 | expect($rootScope.model instanceof Date).toBe(true);
68 | }));
69 |
70 | it('should not parse input when parse disabled', angular.mock.inject(function($rootScope) {
71 | var input = TestUtil.compile('');
72 |
73 | input.val('1999-12-31').triggerHandler('input');
74 | expect(typeof ($rootScope.model) === 'string').toBe(true);
75 | }));
76 |
77 | it('should handle corner cases', angular.mock.inject(function($rootScope) {
78 | var input = TestUtil.compile('');
79 | var model = input.controller('ngModel');
80 |
81 | var tests = [
82 | {modelValue: '', viewValue: ''},
83 | {modelValue: null, viewValue: null},
84 | {}, //tests undefined values
85 | ];
86 |
87 | tests.forEach(function(test) {
88 | $rootScope.model = test.modelValue;
89 | $rootScope.$digest();
90 | expect(model.$viewValue).toBe(null);
91 | });
92 | }));
93 | });
94 |
--------------------------------------------------------------------------------
/src/global/global-masks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var m = angular.module('ui.utils.masks.global', [])
4 | .directive('uiCreditCardMask', require('./credit-card/credit-card'))
5 | .directive('uiDateMask', require('./date/date'))
6 | .directive('uiMoneyMask', require('./money/money'))
7 | .directive('uiNumberMask', require('./number/number'))
8 | .directive('uiPercentageMask', require('./percentage/percentage'))
9 | .directive('uiScientificNotationMask', require('./scientific-notation/scientific-notation'))
10 | .directive('uiTimeMask', require('./time/time'));
11 |
12 | module.exports = m.name;
13 |
--------------------------------------------------------------------------------
/src/global/money/money.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Money Spec
6 |
7 |
8 |
9 |
10 |
18 |
19 |
20 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/global/money/money.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var validators = require('../../helpers/validators');
5 | var PreFormatters = require('../../helpers/pre-formatters');
6 |
7 | function MoneyMaskDirective($locale, $parse) {
8 | return {
9 | restrict: 'A',
10 | require: 'ngModel',
11 | link: function(scope, element, attrs, ctrl) {
12 | var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP,
13 | thousandsDelimiter = $locale.NUMBER_FORMATS.GROUP_SEP,
14 | currencySym = $locale.NUMBER_FORMATS.CURRENCY_SYM,
15 | symbolSeparation = ' ',
16 | decimals = $parse(attrs.uiMoneyMask)(scope),
17 | backspacePressed = false;
18 |
19 | element.bind('keydown keypress', function(event) {
20 | backspacePressed = event.which === 8;
21 | });
22 |
23 | function maskFactory(decimals) {
24 | var decimalsPattern = decimals > 0 ? decimalDelimiter + new Array(decimals + 1).join('0') : '';
25 | var maskPattern = '#' + thousandsDelimiter + '##0' + decimalsPattern;
26 | if (angular.isDefined(attrs.uiCurrencyAfter)) {
27 | maskPattern += symbolSeparation;
28 | } else {
29 | maskPattern = symbolSeparation + maskPattern;
30 | }
31 | return new StringMask(maskPattern, {reverse: true});
32 | }
33 |
34 | if (angular.isDefined(attrs.uiDecimalDelimiter)) {
35 | decimalDelimiter = attrs.uiDecimalDelimiter;
36 | }
37 |
38 | if (angular.isDefined(attrs.uiThousandsDelimiter)) {
39 | thousandsDelimiter = attrs.uiThousandsDelimiter;
40 | }
41 |
42 | if (angular.isDefined(attrs.uiHideGroupSep)) {
43 | thousandsDelimiter = '';
44 | }
45 |
46 | if (angular.isDefined(attrs.uiHideSpace)) {
47 | symbolSeparation = '';
48 | }
49 |
50 | if (angular.isDefined(attrs.currencySymbol)) {
51 | currencySym = attrs.currencySymbol;
52 | if (attrs.currencySymbol.length === 0) {
53 | symbolSeparation = '';
54 | }
55 | }
56 |
57 | if (isNaN(decimals)) {
58 | decimals = 2;
59 | }
60 | decimals = parseInt(decimals);
61 | var moneyMask = maskFactory(decimals);
62 |
63 | function formatter(value) {
64 | if (ctrl.$isEmpty(value)) {
65 | return '';
66 | }
67 |
68 | if (angular.isDefined(attrs.uiIntegerModel)) {
69 | value /= Math.pow(10, decimals);
70 | }
71 |
72 | var prefix = (angular.isDefined(attrs.uiNegativeNumber) && value < 0) ? '-' : '';
73 | var valueToFormat = PreFormatters.prepareNumberToFormatter(value, decimals);
74 |
75 | if (angular.isDefined(attrs.uiCurrencyAfter)) {
76 | return prefix + moneyMask.apply(valueToFormat) + currencySym;
77 | }
78 |
79 | return prefix + currencySym + moneyMask.apply(valueToFormat);
80 | }
81 |
82 | function parser(value) {
83 | if (ctrl.$isEmpty(value)) {
84 | return null;
85 | }
86 |
87 | var actualNumber = value.replace(/[^\d]+/g,''), formatedValue;
88 | actualNumber = actualNumber.replace(/^[0]+([1-9])/,'$1');
89 | actualNumber = actualNumber || '0';
90 |
91 | if (backspacePressed && angular.isDefined(attrs.uiCurrencyAfter) && actualNumber !== 0) {
92 | actualNumber = actualNumber.substring(0, actualNumber.length - 1);
93 | backspacePressed = false;
94 | }
95 |
96 | if (angular.isDefined(attrs.uiCurrencyAfter)) {
97 | formatedValue = moneyMask.apply(actualNumber) + currencySym;
98 | } else {
99 | formatedValue = currencySym + moneyMask.apply(actualNumber);
100 | }
101 |
102 | if (angular.isDefined(attrs.uiNegativeNumber)) {
103 | var isNegative = (value[0] === '-'),
104 | needsToInvertSign = (value.slice(-1) === '-');
105 |
106 | //only apply the minus sign if it is negative or(exclusive)
107 | //needs to be negative and the number is different from zero
108 | if (needsToInvertSign ^ isNegative && !!actualNumber) {
109 | actualNumber *= -1;
110 | formatedValue = '-' + formatedValue;
111 | }
112 | }
113 |
114 | if (value !== formatedValue) {
115 | ctrl.$setViewValue(formatedValue);
116 | ctrl.$render();
117 | }
118 |
119 | var retValue = parseInt(formatedValue.replace(/[^\d\-]+/g,''));
120 |
121 | if (!isNaN(retValue)) {
122 | if (!angular.isDefined(attrs.uiIntegerModel)) {
123 | retValue /= Math.pow(10, decimals);
124 | }
125 |
126 | return retValue;
127 | }
128 |
129 | return null;
130 | }
131 |
132 | ctrl.$formatters.push(formatter);
133 | ctrl.$parsers.push(parser);
134 |
135 | if (attrs.uiMoneyMask) {
136 | scope.$watch(attrs.uiMoneyMask, function(_decimals) {
137 | decimals = isNaN(_decimals) ? 2 : _decimals;
138 | decimals = parseInt(decimals);
139 | moneyMask = maskFactory(decimals);
140 |
141 | parser(ctrl.$viewValue);
142 | });
143 | }
144 |
145 | if (attrs.currency) {
146 | scope.$watch(attrs.currency, function(_currency) {
147 | currencySym = _currency;
148 | moneyMask = maskFactory(decimals);
149 | parser(ctrl.$viewValue);
150 | });
151 | }
152 |
153 | if (attrs.min) {
154 | var minVal;
155 |
156 | ctrl.$validators.min = function(modelValue) {
157 | return validators.minNumber(ctrl, modelValue, minVal);
158 | };
159 |
160 | scope.$watch(attrs.min, function(value) {
161 | minVal = value;
162 | ctrl.$validate();
163 | });
164 | }
165 |
166 | if (attrs.max) {
167 | var maxVal;
168 |
169 | ctrl.$validators.max = function(modelValue) {
170 | return validators.maxNumber(ctrl, modelValue, maxVal);
171 | };
172 |
173 | scope.$watch(attrs.max, function(value) {
174 | maxVal = value;
175 | ctrl.$validate();
176 | });
177 | }
178 | }
179 | };
180 | }
181 | MoneyMaskDirective.$inject = ['$locale', '$parse'];
182 |
183 | module.exports = MoneyMaskDirective;
184 |
--------------------------------------------------------------------------------
/src/global/money/money.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 |
5 | describe('uiMoneyMask', function() {
6 | it('should load the demo page', function() {
7 | browser.get('/src/global/money/money.html');
8 | expect(browser.getTitle()).toEqual('Money Spec');
9 | });
10 |
11 | it('should format money with two decimal places (default)', function() {
12 | var formatterView = new StringMask('#.##0,00', {reverse: true}),
13 | formatterModel = new StringMask('###0.00', {reverse: true}),
14 | numberToFormat = '', currency = 'R$ ', formatedNumberAsString, formatedNumberAsNumber;
15 |
16 | var input = element(by.model('defaultMoney')),
17 | value = element(by.binding('defaultMoney'));
18 |
19 | expect(input.getAttribute('value')).toEqual(currency + '153,12');
20 | input.clear();
21 |
22 | var i;
23 | for (i = 1; i <= 9; i++) {
24 | input.sendKeys(i);
25 | numberToFormat += i;
26 |
27 | formatedNumberAsString = formatterView.apply(numberToFormat);
28 | expect(input.getAttribute('value')).toEqual(currency + formatedNumberAsString);
29 |
30 | formatedNumberAsNumber = formatterModel.apply(numberToFormat);
31 | expect(value.getText()).toEqual(formatedNumberAsNumber);
32 | }
33 |
34 | for (i = 9; i >= 1; i--) {
35 | input.sendKeys(protractor.Key.BACK_SPACE);
36 | numberToFormat = numberToFormat.slice(0, -1);
37 | if (!numberToFormat) {
38 | numberToFormat = '0';
39 | } else {
40 | formatedNumberAsNumber = formatterModel.apply(numberToFormat);
41 | expect(value.getText()).toEqual(formatedNumberAsNumber);
42 | }
43 |
44 | formatedNumberAsString = formatterView.apply(numberToFormat);
45 | expect(input.getAttribute('value')).toEqual(currency + formatedNumberAsString);
46 | }
47 | });
48 |
49 | it('should format a field with 0 as the initial value', function() {
50 | var currency = 'R$ ';
51 |
52 | var input = element(by.model('moneyStartedWith0'));
53 |
54 | expect(input.getAttribute('value')).toEqual(currency+'0,00');
55 | });
56 |
57 | it('should format a field with an initial value with string type', function() {
58 | var currency = 'R$ ';
59 |
60 | var input = element(by.model('moneyInitializedWithString'));
61 |
62 | expect(input.getAttribute('value')).toEqual(currency+'3,53');
63 | });
64 |
65 | it('should format money with three decimal places (parameter)', function() {
66 | var formatterView = new StringMask('#.##0,000', {reverse: true}),
67 | formatterModel = new StringMask('###0.000', {reverse: true}),
68 | numberToFormat = '', currency = 'R$ ', formatedNumberAsString, formatedNumberAsNumber;
69 |
70 | var input = element(by.model('money3Decimals')),
71 | value = element(by.binding('money3Decimals'));
72 |
73 | var i;
74 | for (i = 1; i <= 9; i++) {
75 | input.sendKeys(i);
76 | numberToFormat += i;
77 |
78 | formatedNumberAsString = formatterView.apply(numberToFormat);
79 | expect(input.getAttribute('value')).toEqual(currency + formatedNumberAsString);
80 |
81 | formatedNumberAsNumber = formatterModel.apply(numberToFormat);
82 | expect(value.getText()).toEqual(formatedNumberAsNumber);
83 | }
84 |
85 | for (i = 9; i >= 1; i--) {
86 | input.sendKeys(protractor.Key.BACK_SPACE);
87 | numberToFormat = numberToFormat.slice(0, -1);
88 | if (!numberToFormat) {
89 | numberToFormat = '0';
90 | } else {
91 | formatedNumberAsNumber = formatterModel.apply(numberToFormat);
92 | expect(value.getText()).toEqual(formatedNumberAsNumber);
93 | }
94 |
95 | formatedNumberAsString = formatterView.apply(numberToFormat);
96 | expect(input.getAttribute('value')).toEqual(currency + formatedNumberAsString);
97 | }
98 | });
99 |
100 | it('should convert invalid values to zero', function() {
101 | var currency = 'R$ ';
102 |
103 | var input = element(by.model('defaultMoney'));
104 |
105 | input.clear(); //Clear to send invalid content
106 | input.sendKeys('a'); //Typing invalid content, bug #146
107 |
108 | expect(input.getAttribute('value')).toEqual(currency + '0,00');
109 | });
110 |
111 | it('should add currency after value', function() {
112 | var currency = ' R$';
113 |
114 | var input = element(by.model('currencyAfterValue'));
115 |
116 | input.clear(); //Clear to send invalid content
117 | input.sendKeys('1');
118 |
119 | expect(input.getAttribute('value')).toEqual('0,01' + currency);
120 | });
121 | });
122 |
--------------------------------------------------------------------------------
/src/global/number/number.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Number Spec
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/global/number/number.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var validators = require('../../helpers/validators');
4 | var NumberMasks = require('../../helpers/number-mask-builder');
5 | var PreFormatters = require('../../helpers/pre-formatters');
6 |
7 | function NumberMaskDirective($locale, $parse) {
8 | return {
9 | restrict: 'A',
10 | require: 'ngModel',
11 | link: function(scope, element, attrs, ctrl) {
12 | var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP,
13 | thousandsDelimiter = $locale.NUMBER_FORMATS.GROUP_SEP,
14 | decimals = $parse(attrs.uiNumberMask)(scope);
15 |
16 | if (angular.isDefined(attrs.uiHideGroupSep)) {
17 | thousandsDelimiter = '';
18 | }
19 |
20 | if (isNaN(decimals)) {
21 | decimals = 2;
22 | }
23 |
24 | var viewMask = NumberMasks.viewMask(decimals, decimalDelimiter, thousandsDelimiter),
25 | modelMask = NumberMasks.modelMask(decimals);
26 |
27 | function parser(value) {
28 | if (ctrl.$isEmpty(value)) {
29 | return null;
30 | }
31 |
32 | var valueToFormat = PreFormatters.clearDelimitersAndLeadingZeros(value) || '0';
33 | var formatedValue = viewMask.apply(valueToFormat);
34 | var actualNumber = parseFloat(modelMask.apply(valueToFormat));
35 |
36 | if (angular.isDefined(attrs.uiNegativeNumber)) {
37 | var isNegative = (value[0] === '-'),
38 | needsToInvertSign = (value.slice(-1) === '-');
39 |
40 | //only apply the minus sign if it is negative or(exclusive) or the first character
41 | //needs to be negative and the number is different from zero
42 | if ((needsToInvertSign ^ isNegative) || value === '-') {
43 | actualNumber *= -1;
44 | formatedValue = '-' + ((actualNumber !== 0) ? formatedValue : '');
45 | }
46 | }
47 |
48 | if (ctrl.$viewValue !== formatedValue) {
49 | ctrl.$setViewValue(formatedValue);
50 | ctrl.$render();
51 | }
52 |
53 | return actualNumber;
54 | }
55 |
56 | function formatter(value) {
57 | if (ctrl.$isEmpty(value)) {
58 | return value;
59 | }
60 |
61 | var prefix = (angular.isDefined(attrs.uiNegativeNumber) && value < 0) ? '-' : '';
62 | var valueToFormat = PreFormatters.prepareNumberToFormatter(value, decimals);
63 | return prefix + viewMask.apply(valueToFormat);
64 | }
65 |
66 | function clearViewValueIfMinusSign() {
67 | if (ctrl.$viewValue === '-') {
68 | ctrl.$setViewValue('');
69 | ctrl.$render();
70 | }
71 | }
72 |
73 | element.on('blur', clearViewValueIfMinusSign);
74 |
75 | ctrl.$formatters.push(formatter);
76 | ctrl.$parsers.push(parser);
77 |
78 | if (attrs.uiNumberMask) {
79 | scope.$watch(attrs.uiNumberMask, function(_decimals) {
80 | decimals = isNaN(_decimals) ? 2 : _decimals;
81 | viewMask = NumberMasks.viewMask(decimals, decimalDelimiter, thousandsDelimiter);
82 | modelMask = NumberMasks.modelMask(decimals);
83 |
84 | parser(ctrl.$viewValue);
85 | });
86 | }
87 |
88 | if (attrs.min) {
89 | var minVal;
90 |
91 | ctrl.$validators.min = function(modelValue) {
92 | return validators.minNumber(ctrl, modelValue, minVal);
93 | };
94 |
95 | scope.$watch(attrs.min, function(value) {
96 | minVal = value;
97 | ctrl.$validate();
98 | });
99 | }
100 |
101 | if (attrs.max) {
102 | var maxVal;
103 |
104 | ctrl.$validators.max = function(modelValue) {
105 | return validators.maxNumber(ctrl, modelValue, maxVal);
106 | };
107 |
108 | scope.$watch(attrs.max, function(value) {
109 | maxVal = value;
110 | ctrl.$validate();
111 | });
112 | }
113 | }
114 | };
115 | }
116 | NumberMaskDirective.$inject = ['$locale', '$parse'];
117 |
118 | module.exports = NumberMaskDirective;
119 |
--------------------------------------------------------------------------------
/src/global/number/number.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../global-masks');
4 |
5 | var StringMask = require('string-mask');
6 |
7 | describe('ui-number-mask', function() {
8 | beforeEach(angular.mock.module('ui.utils.masks.global'));
9 |
10 | it('should throw an error if used without ng-model', function() {
11 | expect(function() {
12 | TestUtil.compile('');
13 | }).toThrow();
14 | });
15 |
16 | it('should register a $parser and a $formatter', function() {
17 | var input = TestUtil.compile('');
18 | var model = input.controller('ngModel');
19 |
20 | var maskedInput = TestUtil.compile('');
21 | var maskedModel = maskedInput.controller('ngModel');
22 |
23 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
24 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
25 | });
26 |
27 | it('should format initial model values', function() {
28 | var input = TestUtil.compile('', {
29 | model: '3456.79'
30 | });
31 |
32 | var model = input.controller('ngModel');
33 | expect(model.$viewValue).toBe('3,456.79');
34 | });
35 |
36 | it('should allow negative values if ui-negative-number is defined', function() {
37 | var input = TestUtil.compile('', {
38 | model: '-3456.79'
39 | });
40 |
41 | var model = input.controller('ngModel');
42 | expect(model.$viewValue).toBe('-3,456.79');
43 | });
44 |
45 | it('should hide thousands delimiter when ui-hide-group-sep is present', function() {
46 | var input = TestUtil.compile('', {
47 | model: '3456.79'
48 | });
49 |
50 | var model = input.controller('ngModel');
51 | expect(model.$viewValue).toBe('3456.79');
52 | });
53 |
54 | it('should return null if field is empty', function() {
55 | var input = TestUtil.compile('', {
56 | model: 1000
57 | });
58 |
59 | var model = input.controller('ngModel');
60 | input.val('').triggerHandler('input');
61 |
62 | expect(model.$viewValue).toBe('');
63 | expect(model.$modelValue).toBeNull();
64 | expect(model.$valid).toBe(true);
65 |
66 | });
67 |
68 | it('should validate minimum value', function() {
69 | var input = TestUtil.compile('', {
70 | model: '3456.79'
71 | });
72 |
73 | var model = input.controller('ngModel');
74 | expect(model.$viewValue).toBe('3,456.79');
75 | expect(model.$valid).toBe(true);
76 | input.val('12.34').triggerHandler('input');
77 | expect(model.$valid).toBe(false);
78 | input.val('129.34').triggerHandler('input');
79 | expect(model.$valid).toBe(true);
80 | });
81 |
82 | it('should validate maximum value', function() {
83 | var input = TestUtil.compile('', {
84 | model: '3456.79'
85 | });
86 |
87 | var model = input.controller('ngModel');
88 | expect(model.$viewValue).toBe('3,456.79');
89 | expect(model.$valid).toBe(false);
90 | input.val('12.34').triggerHandler('input');
91 | expect(model.$valid).toBe(true);
92 | input.val('129.34').triggerHandler('input');
93 | expect(model.$valid).toBe(false);
94 | });
95 |
96 | it('should format number with three decimal places', function() {
97 | var input = TestUtil.compile('');
98 | var model = input.controller('ngModel');
99 |
100 | var formatterView = new StringMask('#,##0.000', {reverse: true}),
101 | formatterModel = new StringMask('###0.000', {reverse: true}),
102 | numberToFormat = '';
103 |
104 | for (var i = 1; i <= 9; i++) {
105 | numberToFormat += i;
106 | input.val(numberToFormat).triggerHandler('input');
107 | expect(model.$viewValue).toBe(formatterView.apply(numberToFormat));
108 | expect(model.$modelValue).toBe(parseFloat(formatterModel.apply(numberToFormat)));
109 | }
110 | });
111 |
112 | it('should handle corner cases', angular.mock.inject(function($rootScope) {
113 | var input = TestUtil.compile('');
114 | var model = input.controller('ngModel');
115 |
116 | var tests = [
117 | {modelValue: '', viewValue: ''},
118 | {modelValue: '0', viewValue: '0.00'},
119 | {modelValue: '0.0', viewValue: '0.00'},
120 | {modelValue: 0, viewValue: '0.00'}
121 | ];
122 |
123 | tests.forEach(function(test) {
124 | $rootScope.model = test.modelValue;
125 | $rootScope.$digest();
126 | expect(model.$viewValue).toBe(test.viewValue);
127 | });
128 | }));
129 |
130 | it('should show zero when the model value is zero and the precision is set to 0', angular.mock.inject(function($rootScope) {
131 | var input = TestUtil.compile('');
132 | var model = input.controller('ngModel');
133 |
134 |
135 | $rootScope.model = 0;
136 | $rootScope.$digest();
137 | expect(model.$viewValue).toBe('0');
138 | }));
139 |
140 | it('should accept negative numbers if "ui-negative-number" is defined', function() {
141 | var input = TestUtil.compile('');
142 | var model = input.controller('ngModel');
143 |
144 | input.val('-1234.56').triggerHandler('input');
145 | expect(model.$viewValue).toBe('-1,234.56');
146 | expect(model.$modelValue).toBe(-1234.56);
147 | input.val('-1,234.56-').triggerHandler('input');
148 | expect(model.$viewValue).toBe('1,234.56');
149 | expect(model.$modelValue).toBe(1234.56);
150 | input.val('1,234.56-').triggerHandler('input');
151 | expect(model.$viewValue).toBe('-1,234.56');
152 | expect(model.$modelValue).toBe(-1234.56);
153 | });
154 |
155 | it('should clear field on blur if it contains only minus sign', function() {
156 | var input = TestUtil.compile('');
157 | var model = input.controller('ngModel');
158 |
159 | input.val('-').triggerHandler('input');
160 | expect(model.$viewValue).toBe('-');
161 | expect(model.$modelValue).toBe(0);
162 | input.triggerHandler('blur');
163 | expect(model.$viewValue).toBe('');
164 | expect(model.$modelValue).toBe(null);
165 | });
166 | });
167 |
--------------------------------------------------------------------------------
/src/global/percentage/percentage.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Percentage Spec
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/global/percentage/percentage.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var validators = require('../../helpers/validators');
4 | var NumberMasks = require('../../helpers/number-mask-builder');
5 | var PreFormatters = require('../../helpers/pre-formatters');
6 |
7 | function preparePercentageToFormatter(value, decimals, modelMultiplier) {
8 | return PreFormatters.clearDelimitersAndLeadingZeros((parseFloat(value)*modelMultiplier).toFixed(decimals));
9 | }
10 |
11 | function PercentageMaskDirective($locale) {
12 | return {
13 | restrict: 'A',
14 | require: 'ngModel',
15 | link: function(scope, element, attrs, ctrl) {
16 | var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP;
17 |
18 | var backspacePressed = false;
19 | element.bind('keydown keypress', function(event) {
20 | backspacePressed = event.which === 8;
21 | });
22 |
23 | var thousandsDelimiter = $locale.NUMBER_FORMATS.GROUP_SEP;
24 | if (angular.isDefined(attrs.uiHideGroupSep)) {
25 | thousandsDelimiter = '';
26 | }
27 |
28 | var percentageSymbol = ' %';
29 | if (angular.isDefined(attrs.uiHidePercentageSign)) {
30 | percentageSymbol = '';
31 | } else if (angular.isDefined(attrs.uiHideSpace)) {
32 | percentageSymbol = '%';
33 | }
34 |
35 | var decimals = parseInt(attrs.uiPercentageMask);
36 | if (isNaN(decimals)) {
37 | decimals = 2;
38 | }
39 |
40 | var modelValue = {
41 | multiplier : 100,
42 | decimalMask: 2
43 | };
44 | if (angular.isDefined(attrs.uiPercentageValue)) {
45 | modelValue.multiplier = 1;
46 | modelValue.decimalMask = 0;
47 | }
48 |
49 | var numberDecimals = decimals + modelValue.decimalMask;
50 | var viewMask = NumberMasks.viewMask(decimals, decimalDelimiter, thousandsDelimiter),
51 | modelMask = NumberMasks.modelMask(numberDecimals);
52 |
53 | function formatter(value) {
54 | if (ctrl.$isEmpty(value)) {
55 | return value;
56 | }
57 | var prefix = (angular.isDefined(attrs.uiNegativeNumber) && value < 0) ? '-' : '';
58 | var valueToFormat = preparePercentageToFormatter(value, decimals, modelValue.multiplier);
59 | var formatedValue = prefix + viewMask.apply(valueToFormat) + percentageSymbol;
60 |
61 | return formatedValue;
62 | }
63 |
64 | function parser(value) {
65 | if (ctrl.$isEmpty(value)) {
66 | return null;
67 | }
68 |
69 | var valueToFormat = PreFormatters.clearDelimitersAndLeadingZeros(value) || '0';
70 | if (percentageSymbol !== '' && value.length > 1 && value.indexOf('%') === -1) {
71 | valueToFormat = valueToFormat.slice(0, valueToFormat.length - 1);
72 | }
73 |
74 | if (backspacePressed && value.length === 1 && value !== '%') {
75 | valueToFormat = '0';
76 | }
77 |
78 | var formatedValue = viewMask.apply(valueToFormat) + percentageSymbol;
79 | var actualNumber = parseFloat(modelMask.apply(valueToFormat));
80 |
81 | if (angular.isDefined(attrs.uiNegativeNumber)) {
82 | var isNegative = (value[0] === '-'),
83 | needsToInvertSign = (value.slice(-1) === '-');
84 |
85 | //only apply the minus sign if it is negative or(exclusive) or the first character
86 | //needs to be negative and the number is different from zero
87 | if ((needsToInvertSign ^ isNegative) || value === '-') {
88 | actualNumber *= -1;
89 | formatedValue = '-' + ((actualNumber !== 0) ? formatedValue : '');
90 | }
91 | }
92 |
93 | if (ctrl.$viewValue !== formatedValue) {
94 | ctrl.$setViewValue(formatedValue);
95 | ctrl.$render();
96 | }
97 |
98 | return actualNumber;
99 | }
100 |
101 | ctrl.$formatters.push(formatter);
102 | ctrl.$parsers.push(parser);
103 |
104 | if (attrs.uiPercentageMask) {
105 | scope.$watch(attrs.uiPercentageMask, function(_decimals) {
106 | decimals = isNaN(_decimals) ? 2 : _decimals;
107 |
108 | numberDecimals = decimals + modelValue.decimalMask;
109 | viewMask = NumberMasks.viewMask(decimals, decimalDelimiter, thousandsDelimiter);
110 | modelMask = NumberMasks.modelMask(numberDecimals);
111 |
112 | parser(formatter(ctrl.$modelValue));
113 | });
114 | }
115 |
116 | if (attrs.min) {
117 | var minVal;
118 |
119 | ctrl.$validators.min = function(modelValue) {
120 | return validators.minNumber(ctrl, modelValue, minVal);
121 | };
122 |
123 | scope.$watch(attrs.min, function(value) {
124 | minVal = value;
125 | ctrl.$validate();
126 | });
127 | }
128 |
129 | if (attrs.max) {
130 | var maxVal;
131 |
132 | ctrl.$validators.max = function(modelValue) {
133 | return validators.maxNumber(ctrl, modelValue, maxVal);
134 | };
135 |
136 | scope.$watch(attrs.max, function(value) {
137 | maxVal = value;
138 | ctrl.$validate();
139 | });
140 | }
141 | }
142 | };
143 | }
144 | PercentageMaskDirective.$inject = ['$locale'];
145 |
146 | module.exports = PercentageMaskDirective;
147 |
--------------------------------------------------------------------------------
/src/global/scientific-notation/scientific-notation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Scientific Notation Spec
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/global/scientific-notation/scientific-notation.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 |
5 | function ScientificNotationMaskDirective($locale, $parse) {
6 | var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP,
7 | defaultPrecision = 2;
8 |
9 | function significandMaskBuilder(decimals) {
10 | var mask = '0';
11 |
12 | if (decimals > 0) {
13 | mask += decimalDelimiter;
14 | for (var i = 0; i < decimals; i++) {
15 | mask += '0';
16 | }
17 | }
18 |
19 | return new StringMask(mask, {
20 | reverse: true
21 | });
22 | }
23 |
24 | return {
25 | restrict: 'A',
26 | require: 'ngModel',
27 | link: function(scope, element, attrs, ctrl) {
28 | var decimals = $parse(attrs.uiScientificNotationMask)(scope);
29 |
30 | if (isNaN(decimals)) {
31 | decimals = defaultPrecision;
32 | }
33 |
34 | var significandMask = significandMaskBuilder(decimals);
35 |
36 | function splitNumber(value) {
37 | var stringValue = value.toString(),
38 | splittedNumber = stringValue.match(/(-?[0-9]*)[\.]?([0-9]*)?[Ee]?([\+-]?[0-9]*)?/);
39 |
40 | return {
41 | integerPartOfSignificand: splittedNumber[1],
42 | decimalPartOfSignificand: splittedNumber[2],
43 | exponent: splittedNumber[3] | 0
44 | };
45 | }
46 |
47 | function formatter(value) {
48 | if (ctrl.$isEmpty(value)) {
49 | return value;
50 | }
51 |
52 | if (typeof value === 'number') {
53 | value = value.toExponential(decimals);
54 | } else {
55 | value = value.toString().replace(decimalDelimiter, '.');
56 | }
57 |
58 | var formattedValue, exponent;
59 | var splittedNumber = splitNumber(value);
60 |
61 | var integerPartOfSignificand = splittedNumber.integerPartOfSignificand || 0;
62 | var numberToFormat = integerPartOfSignificand.toString();
63 | if (angular.isDefined(splittedNumber.decimalPartOfSignificand)) {
64 | numberToFormat += splittedNumber.decimalPartOfSignificand;
65 | }
66 |
67 | var needsNormalization =
68 | (integerPartOfSignificand >= 1 || integerPartOfSignificand <= -1) &&
69 | (
70 | (angular.isDefined(splittedNumber.decimalPartOfSignificand) &&
71 | splittedNumber.decimalPartOfSignificand.length > decimals) ||
72 | (decimals === 0 && numberToFormat.length >= 2)
73 | );
74 |
75 | if (needsNormalization) {
76 | exponent = numberToFormat.slice(decimals + 1, numberToFormat.length);
77 | numberToFormat = numberToFormat.slice(0, decimals + 1);
78 | }
79 |
80 | formattedValue = significandMask.apply(numberToFormat);
81 |
82 | if (splittedNumber.exponent !== 0) {
83 | exponent = splittedNumber.exponent;
84 | }
85 |
86 | if (angular.isDefined(exponent)) {
87 | formattedValue += 'e' + exponent;
88 | }
89 |
90 | var prefix = (angular.isDefined(attrs.uiNegativeNumber) && value[0] === '-') ? '-' : '';
91 |
92 | return prefix + formattedValue;
93 | }
94 |
95 | function parser(value) {
96 | if (ctrl.$isEmpty(value)) {
97 | return value;
98 | }
99 |
100 | var isExponentNegative = /e-/.test(value);
101 | var cleanValue = value.replace('e-', 'e');
102 | var viewValue = formatter(cleanValue);
103 |
104 | var needsToInvertSign = (value.slice(-1) === '-');
105 |
106 | if (needsToInvertSign ^ isExponentNegative) {
107 | viewValue = viewValue.replace(/(e[-]?)/, 'e-');
108 | }
109 |
110 | if (needsToInvertSign && isExponentNegative) {
111 | viewValue = viewValue[0] !== '-' ? ('-' + viewValue) : viewValue.replace(/^(-)/,'');
112 | }
113 |
114 | var modelValue = parseFloat(viewValue.replace(decimalDelimiter, '.'));
115 |
116 | if (ctrl.$viewValue !== viewValue) {
117 | ctrl.$setViewValue(viewValue);
118 | ctrl.$render();
119 | }
120 |
121 | return modelValue;
122 | }
123 |
124 | ctrl.$formatters.push(formatter);
125 | ctrl.$parsers.push(parser);
126 |
127 | ctrl.$validators.max = function validator(value) {
128 | return ctrl.$isEmpty(value) || value < Number.MAX_VALUE;
129 | };
130 | }
131 | };
132 | }
133 | ScientificNotationMaskDirective.$inject = ['$locale', '$parse'];
134 |
135 | module.exports = ScientificNotationMaskDirective;
136 |
--------------------------------------------------------------------------------
/src/global/scientific-notation/scientific-notation.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../global-masks');
4 |
5 | describe('uiScientificNotationMask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.global'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: 12345.67890123456
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('1.23e4');
32 | });
33 |
34 | it('should format initial model values with 0 decimals', function() {
35 | var input = TestUtil.compile('', {
36 | model: 12345.67890123456
37 | });
38 |
39 | var model = input.controller('ngModel');
40 | expect(model.$viewValue).toBe('1e4');
41 | });
42 |
43 | it('should format initial model values with 3 decimals', function() {
44 | var input = TestUtil.compile('', {
45 | model: 12345.67890123456
46 | });
47 |
48 | var model = input.controller('ngModel');
49 | expect(model.$viewValue).toBe('1.235e4');
50 | });
51 |
52 | it('should format initial model values with negative exponent', function() {
53 | var input = TestUtil.compile('', {
54 | model: 1.3456e-3
55 | });
56 |
57 | var model = input.controller('ngModel');
58 | expect(model.$viewValue).toBe('1.35e-3');
59 | });
60 |
61 | it('should format input', function() {
62 | var input = TestUtil.compile('');
63 | var model = input.controller('ngModel');
64 |
65 | input.val('123').triggerHandler('input');
66 | expect(model.$viewValue).toBe('1.23');
67 | expect(model.$modelValue).toBe(1.23);
68 | input.val('1.2345').triggerHandler('input');
69 | expect(model.$viewValue).toBe('1.23e45');
70 | expect(model.$modelValue).toBe(1.23e45);
71 | input.val('1.2345e-9').triggerHandler('input');
72 | expect(model.$viewValue).toBe('1.23e-9');
73 | expect(model.$modelValue).toBe(1.23e-9);
74 | });
75 |
76 | it('should accept negative numbers if "ui-negative-number" is defined', function() {
77 | var input = TestUtil.compile('', {
78 | model: -1.3456e3
79 | });
80 | var model = input.controller('ngModel');
81 |
82 | expect(model.$viewValue).toBe('-1.35e3');
83 | expect(model.$modelValue).toBe(-1345.6);
84 |
85 | input.val('-1.23e45').triggerHandler('input');
86 | expect(model.$viewValue).toBe('-1.23e45');
87 | expect(model.$modelValue).toBe(-1.23e45);
88 |
89 | input.val('1.23e45-').triggerHandler('input');
90 | expect(model.$viewValue).toBe('1.23e-45');
91 | expect(model.$modelValue).toBe(1.23e-45);
92 |
93 | input.val('1.23e-45-').triggerHandler('input');
94 | expect(model.$viewValue).toBe('-1.23e45');
95 | expect(model.$modelValue).toBe(-1.23e45);
96 |
97 | input.val('-1.23e45-').triggerHandler('input');
98 | expect(model.$viewValue).toBe('-1.23e-45');
99 | expect(model.$modelValue).toBe(-1.23e-45);
100 |
101 | input.val('-1.23e-45-').triggerHandler('input');
102 | expect(model.$viewValue).toBe('1.23e45');
103 | expect(model.$modelValue).toBe(1.23e45);
104 | });
105 |
106 | it('should handle corner cases', angular.mock.inject(function($rootScope) {
107 | var input = TestUtil.compile('');
108 | var model = input.controller('ngModel');
109 |
110 | var tests = [
111 | {modelValue: '', viewValue: ''},
112 | {modelValue: '0', viewValue: '0.00'},
113 | {modelValue: '0.0', viewValue: '0.00'},
114 | {modelValue: '.0', viewValue: '0.00'},
115 | {modelValue: 0, viewValue: '0.00'}
116 | ];
117 |
118 | tests.forEach(function(test) {
119 | $rootScope.model = test.modelValue;
120 | $rootScope.$digest();
121 | expect(model.$viewValue).toBe(test.viewValue);
122 | });
123 |
124 | input.val('').triggerHandler('input');
125 | expect(model.$viewValue).toBe('');
126 | }));
127 | });
128 |
--------------------------------------------------------------------------------
/src/global/time/time.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Time Spec
6 |
7 |
8 |
9 |
16 |
17 |
18 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/global/time/time.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 |
5 | module.exports = function TimeMaskDirective() {
6 | return {
7 | restrict: 'A',
8 | require: 'ngModel',
9 | link: function(scope, element, attrs, ctrl) {
10 | var timeFormat = '00:00:00';
11 |
12 | if (angular.isDefined(attrs.uiTimeMask) && attrs.uiTimeMask === 'short') {
13 | timeFormat = '00:00';
14 | }
15 |
16 | var formattedValueLength = timeFormat.length;
17 | var unformattedValueLength = timeFormat.replace(':', '').length;
18 | var timeMask = new StringMask(timeFormat);
19 |
20 | function formatter(value) {
21 | if (ctrl.$isEmpty(value)) {
22 | return value;
23 | }
24 |
25 | var cleanValue = value.replace(/[^0-9]/g, '').slice(0, unformattedValueLength) || '';
26 | return (timeMask.apply(cleanValue) || '').replace(/[^0-9]$/, '');
27 | }
28 |
29 | ctrl.$formatters.push(formatter);
30 |
31 | ctrl.$parsers.push(function parser(value) {
32 | if (ctrl.$isEmpty(value)) {
33 | return value;
34 | }
35 |
36 | var viewValue = formatter(value);
37 | var modelValue = viewValue;
38 |
39 | if (ctrl.$viewValue !== viewValue) {
40 | ctrl.$setViewValue(viewValue);
41 | ctrl.$render();
42 | }
43 |
44 | return modelValue;
45 | });
46 |
47 | ctrl.$validators.time = function(modelValue) {
48 | if (ctrl.$isEmpty(modelValue)) {
49 | return true;
50 | }
51 |
52 | var splittedValue = modelValue.toString().split(/:/).filter(function(v) {
53 | return !!v;
54 | });
55 |
56 | var hours = parseInt(splittedValue[0]),
57 | minutes = parseInt(splittedValue[1]),
58 | seconds = parseInt(splittedValue[2] || 0);
59 |
60 | return modelValue.toString().length === formattedValueLength &&
61 | hours < 24 && minutes < 60 && seconds < 60;
62 | };
63 | }
64 | };
65 | };
66 |
--------------------------------------------------------------------------------
/src/global/time/time.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 |
5 | describe('uiTimeMask', function() {
6 | it('should load the demo page', function() {
7 | browser.get('/src/global/time/time.html');
8 | expect(browser.getTitle()).toEqual('Time Spec');
9 | });
10 |
11 | describe('full mode: HH:MM:SS (default)', function() {
12 | it('should format a time input with default ("00:00:00") format', function() {
13 | var timeFormatter = new StringMask('00:00:00'),
14 | formatedTimeAsString, numberToFormat = '', inputKeysToSend = '235959';
15 |
16 | var input = element(by.model('timeMask')),
17 | value = element(by.exactBinding('timeMask'));
18 |
19 | var i;
20 | for (i = 0; i < 6; i++) {
21 | var key = inputKeysToSend.charAt(i);
22 | input.sendKeys(key);
23 | numberToFormat += key;
24 | formatedTimeAsString = timeFormatter.apply(numberToFormat).replace(/:$/,'');
25 | expect(input.getAttribute('value')).toEqual(formatedTimeAsString);
26 | expect(value.getText()).toEqual(formatedTimeAsString);
27 | }
28 |
29 | for (i = 5; i >= 0; i--) {
30 | input.sendKeys(protractor.Key.BACK_SPACE);
31 | numberToFormat = numberToFormat.slice(0, -1);
32 | if (numberToFormat) {
33 | formatedTimeAsString = timeFormatter.apply(numberToFormat).replace(/:$/,'');
34 | expect(input.getAttribute('value')).toEqual(formatedTimeAsString);
35 | expect(value.getText()).toEqual(formatedTimeAsString);
36 | }
37 | }
38 | });
39 |
40 | it('should be valid if the model is a valid time', function() {
41 | var inputKeysToSend = '235959';
42 |
43 | var input = element(by.model('timeMask')),
44 | valid = element(by.binding('form.timeMaskInput.$error'));
45 |
46 | var i;
47 | for (i = 0; i < 5; i++) {
48 | input.sendKeys(inputKeysToSend.charAt(i));
49 | expect(valid.getText()).toEqual('{ "time": true }');
50 | }
51 |
52 | input.sendKeys(inputKeysToSend.charAt(5));
53 | expect(valid.getText()).toEqual('{}');
54 |
55 | for (i = 5; i > 0; i--) {
56 | input.sendKeys(protractor.Key.BACK_SPACE);
57 | expect(valid.getText()).toEqual('{ "time": true }');
58 | }
59 |
60 | input.sendKeys(protractor.Key.BACK_SPACE);
61 | expect(valid.getText()).toEqual('{}');
62 | });
63 | });
64 |
65 | describe('short mode: HH:MM', function() {
66 | it('should format a time input with "00:00" format', function() {
67 | var timeFormatter = new StringMask('00:00'),
68 | formatedTimeAsString, numberToFormat = '', inputKeysToSend = '2359';
69 |
70 | var input = element(by.model('shortTimeMask')),
71 | value = element(by.exactBinding('shortTimeMask'));
72 |
73 | var i;
74 | for (i = 0; i < 4; i++) {
75 | var key = inputKeysToSend.charAt(i);
76 | input.sendKeys(key);
77 | numberToFormat += key;
78 | formatedTimeAsString = timeFormatter.apply(numberToFormat).replace(/:$/,'');
79 | expect(input.getAttribute('value')).toEqual(formatedTimeAsString);
80 | expect(value.getText()).toEqual(formatedTimeAsString);
81 | }
82 |
83 | for (i = 3; i >= 0; i--) {
84 | input.sendKeys(protractor.Key.BACK_SPACE);
85 | numberToFormat = numberToFormat.slice(0, -1);
86 | if (numberToFormat) {
87 | formatedTimeAsString = timeFormatter.apply(numberToFormat).replace(/:$/,'');
88 | expect(input.getAttribute('value')).toEqual(formatedTimeAsString);
89 | expect(value.getText()).toEqual(formatedTimeAsString);
90 | }
91 | }
92 | });
93 |
94 | it('should be valid if the model is a valid time', function() {
95 | var inputKeysToSend = '235959';
96 |
97 | var input = element(by.model('shortTimeMask')),
98 | valid = element(by.binding('form.shortTimeMaskInput.$error'));
99 |
100 | var i;
101 | for (i = 0; i < 3; i++) {
102 | input.sendKeys(inputKeysToSend.charAt(i));
103 | expect(valid.getText()).toEqual('{ "time": true }');
104 | }
105 |
106 | input.sendKeys(inputKeysToSend.charAt(3));
107 | expect(valid.getText()).toEqual('{}');
108 |
109 | for (i = 3; i > 0; i--) {
110 | input.sendKeys(protractor.Key.BACK_SPACE);
111 | expect(valid.getText()).toEqual('{ "time": true }');
112 | }
113 |
114 | input.sendKeys(protractor.Key.BACK_SPACE);
115 | expect(valid.getText()).toEqual('{}');
116 | });
117 | });
118 | });
119 |
--------------------------------------------------------------------------------
/src/global/time/time.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../global-masks');
4 |
5 | describe('ui-time-mask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.global'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: '142645'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('14:26:45');
32 | });
33 |
34 | it('should format initial model values (short mode)', function() {
35 | var input = TestUtil.compile('', {
36 | model: '142645'
37 | });
38 |
39 | var model = input.controller('ngModel');
40 | expect(model.$viewValue).toBe('14:26');
41 | });
42 |
43 | it('should ignore non digits', function() {
44 | var input = TestUtil.compile('');
45 | var model = input.controller('ngModel');
46 |
47 | var tests = [
48 | {value:'@', viewValue:'', modelValue:''},
49 | {value:'2-', viewValue:'2', modelValue:'2'},
50 | {value:'23a', viewValue:'23', modelValue:'23'},
51 | {value:'23_34', viewValue:'23:34', modelValue:'23:34'},
52 | {value:'23346!324', viewValue:'23:34:63', modelValue:'23:34:63'}
53 | ];
54 |
55 | tests.forEach(function(test) {
56 | input.val(test.value).triggerHandler('input');
57 | expect(model.$viewValue).toBe(test.viewValue);
58 | expect(model.$modelValue).toBe(test.modelValue);
59 | });
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/src/helpers/mask-factory.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function maskFactory(maskDefinition) {
4 | return function MaskDirective() {
5 | return {
6 | restrict: 'A',
7 | require: 'ngModel',
8 | link: function(scope, element, attrs, ctrl) {
9 | ctrl.$formatters.push(function formatter(value) {
10 | if (ctrl.$isEmpty(value)) {
11 | return value;
12 | }
13 |
14 | var cleanValue = maskDefinition.clearValue(value.toString(), attrs);
15 | return maskDefinition.format(cleanValue);
16 | });
17 |
18 | ctrl.$parsers.push(function parser(value) {
19 | if (ctrl.$isEmpty(value)) {
20 | return value;
21 | }
22 |
23 | var cleanValue = maskDefinition.clearValue(value.toString(), attrs);
24 | var formattedValue = maskDefinition.format(cleanValue);
25 |
26 | if (ctrl.$viewValue !== formattedValue) {
27 | ctrl.$setViewValue(formattedValue);
28 | ctrl.$render();
29 | }
30 |
31 | if (angular.isUndefined(maskDefinition.getModelValue)) {
32 | return cleanValue;
33 | }
34 |
35 | var actualModelType = typeof ctrl.$modelValue;
36 | return maskDefinition.getModelValue(formattedValue, actualModelType);
37 | });
38 |
39 | angular.forEach(maskDefinition.validations, function(validatorFn, validationErrorKey) {
40 | ctrl.$validators[validationErrorKey] = function validator(modelValue, viewValue) {
41 | return ctrl.$isEmpty(modelValue) || validatorFn(modelValue, viewValue, attrs);
42 | };
43 | });
44 | }
45 | };
46 | };
47 | };
48 |
--------------------------------------------------------------------------------
/src/helpers/number-mask-builder.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 |
5 | function viewMask(decimals, decimalDelimiter, thousandsDelimiter) {
6 | var mask = '#' + thousandsDelimiter + '##0';
7 |
8 | if (decimals > 0) {
9 | mask += decimalDelimiter;
10 | for (var i = 0; i < decimals; i++) {
11 | mask += '0';
12 | }
13 | }
14 |
15 | return new StringMask(mask, {
16 | reverse: true
17 | });
18 | }
19 |
20 | function modelMask(decimals) {
21 | var mask = '###0';
22 |
23 | if (decimals > 0) {
24 | mask += '.';
25 | for (var i = 0; i < decimals; i++) {
26 | mask += '0';
27 | }
28 | }
29 |
30 | return new StringMask(mask, {
31 | reverse: true
32 | });
33 | }
34 |
35 | module.exports = {
36 | viewMask: viewMask,
37 | modelMask: modelMask
38 | };
39 |
--------------------------------------------------------------------------------
/src/helpers/pre-formatters.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function clearDelimitersAndLeadingZeros(value) {
4 | if (value === '0') {
5 | return '0';
6 | }
7 |
8 | var cleanValue = value.toString().replace(/^-/,'').replace(/^0*/, '');
9 | return cleanValue.replace(/[^0-9]/g, '');
10 | }
11 |
12 | function prepareNumberToFormatter(value, decimals) {
13 | return clearDelimitersAndLeadingZeros((parseFloat(value)).toFixed(decimals));
14 | }
15 |
16 | module.exports = {
17 | clearDelimitersAndLeadingZeros: clearDelimitersAndLeadingZeros,
18 | prepareNumberToFormatter: prepareNumberToFormatter
19 | };
20 |
--------------------------------------------------------------------------------
/src/helpers/validators.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | maxNumber: function(ctrl, value, limit) {
5 | var max = parseFloat(limit, 10);
6 | return ctrl.$isEmpty(value) || isNaN(max) || value <= max;
7 | },
8 | minNumber: function(ctrl, value, limit) {
9 | var min = parseFloat(limit, 10);
10 | return ctrl.$isEmpty(value) || isNaN(min) || value >= min;
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/src/us/phone/us-phone.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | US Phone Number Spec
6 |
7 |
8 |
9 |
15 |
16 |
17 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/us/phone/us-phone.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var StringMask = require('string-mask');
4 | var maskFactory = require('../../helpers/mask-factory');
5 |
6 | var phoneMaskUS = new StringMask('(000) 000-0000'),
7 | phoneMaskINTL = new StringMask('+00-00-000-000000');
8 |
9 | module.exports = maskFactory({
10 | clearValue: function(rawValue) {
11 | return rawValue.toString().replace(/[^0-9]/g, '');
12 | },
13 | format: function(cleanValue) {
14 | var formattedValue;
15 |
16 | if (cleanValue.length < 11) {
17 | formattedValue = phoneMaskUS.apply(cleanValue) || '';
18 | } else {
19 | formattedValue = phoneMaskINTL.apply(cleanValue);
20 | }
21 |
22 | return formattedValue.trim().replace(/[^0-9]$/, '');
23 | },
24 | validations: {
25 | usPhoneNumber: function(value) {
26 | return value && value.toString().length > 9;
27 | }
28 | }
29 | });
30 |
--------------------------------------------------------------------------------
/src/us/phone/us-phone.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('uiUsPhoneNumberMask', function() {
4 | it('should load the demo page', function() {
5 | browser.get('/src/us/phone/us-phone.html');
6 | expect(browser.getTitle()).toEqual('US Phone Number Spec');
7 | });
8 |
9 | var runTests = function(input, value) {
10 | var BS = protractor.Key.BACK_SPACE;
11 | var tests = [
12 | {key: '1', viewValue: '(1', modelValue: '1'},
13 | {key: '2', viewValue: '(12', modelValue: '12'},
14 | {key: '3', viewValue: '(123', modelValue: '123'},
15 | {key: '4', viewValue: '(123) 4', modelValue: '1234'},
16 | {key: '5', viewValue: '(123) 45', modelValue: '12345'},
17 | {key: '6', viewValue: '(123) 456', modelValue: '123456'},
18 | {key: '7', viewValue: '(123) 456-7', modelValue: '1234567'},
19 | {key: '8', viewValue: '(123) 456-78', modelValue: '12345678'},
20 | {key: '9', viewValue: '(123) 456-789', modelValue: '123456789'},
21 | {key: '0', viewValue: '(123) 456-7890', modelValue: '1234567890'},
22 | {key: '1', viewValue: '+12-34-567-8901', modelValue: '12345678901'},
23 | {key: '2', viewValue: '+12-34-567-89012', modelValue: '123456789012'},
24 | {key: '3', viewValue: '+12-34-567-890123', modelValue: '1234567890123'},
25 | {key: BS, viewValue: '+12-34-567-89012', modelValue: '123456789012'},
26 | {key: BS, viewValue: '+12-34-567-8901', modelValue: '12345678901'},
27 | {key: BS, viewValue: '(123) 456-7890', modelValue: '1234567890'},
28 | {key: BS, viewValue: '(123) 456-789', modelValue: '123456789'},
29 | {key: BS, viewValue: '(123) 456-78', modelValue: '12345678'},
30 | {key: BS, viewValue: '(123) 456-7', modelValue: '1234567'},
31 | {key: BS, viewValue: '(123) 456', modelValue: '123456'},
32 | {key: BS, viewValue: '(123) 45', modelValue: '12345'},
33 | {key: BS, viewValue: '(123) 4', modelValue: '1234'},
34 | {key: BS, viewValue: '(123', modelValue: '123'},
35 | {key: BS, viewValue: '(12', modelValue: '12'},
36 | {key: BS, viewValue: '(1', modelValue: '1'},
37 | {key: BS, viewValue: '', modelValue: ''},
38 | ];
39 |
40 | for (var i = 0; i < tests.length; i++) {
41 | input.sendKeys(tests[i].key);
42 | expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
43 | expect(value.getText()).toEqual(tests[i].modelValue);
44 | }
45 | };
46 |
47 | it('should apply a phone number mask while the user is typing:', function() {
48 | var input = element(by.id('us-phone-input')),
49 | value = element(by.id('us-phone-value'));
50 |
51 | runTests(input, value);
52 | });
53 |
54 | it('should apply a phone number mask in a model with default value:', function() {
55 | var input = element(by.id('init-us-phone-input')),
56 | value = element(by.id('init-us-phone-value'));
57 |
58 | expect(input.getAttribute('value')).toEqual('(313) 353-6767');
59 | input.clear();
60 |
61 | runTests(input, value);
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/src/us/phone/us-phone.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('../us-masks');
4 |
5 | describe('uiUsPhoneNumberMask', function() {
6 | beforeEach(angular.mock.module('ui.utils.masks.us'));
7 |
8 | it('should throw an error if used without ng-model', function() {
9 | expect(function() {
10 | TestUtil.compile('');
11 | }).toThrow();
12 | });
13 |
14 | it('should register a $parser and a $formatter', function() {
15 | var input = TestUtil.compile('');
16 | var model = input.controller('ngModel');
17 |
18 | var maskedInput = TestUtil.compile('');
19 | var maskedModel = maskedInput.controller('ngModel');
20 |
21 | expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
22 | expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
23 | });
24 |
25 | it('should format initial model values', function() {
26 | var input = TestUtil.compile('', {
27 | model: '3011201034'
28 | });
29 |
30 | var model = input.controller('ngModel');
31 | expect(model.$viewValue).toBe('(301) 120-1034');
32 | });
33 |
34 | it('should ignore non digits', function() {
35 | var input = TestUtil.compile('');
36 | var model = input.controller('ngModel');
37 |
38 | var tests = [
39 | {value:'@', viewValue:'', modelValue:''},
40 | {value:'2-', viewValue:'(2', modelValue:'2'},
41 | {value:'23a', viewValue:'(23', modelValue:'23'},
42 | {value:'23_34', viewValue:'(233) 4', modelValue:'2334'},
43 | {value:'23346!', viewValue:'(233) 46', modelValue:'23346'},
44 | {value:'23346!32400', viewValue:'(233) 463-2400', modelValue:'2334632400'},
45 | {value:'23346!32400932', viewValue:'+23-34-632-400932', modelValue:'2334632400932'},
46 | ];
47 |
48 | tests.forEach(function(test) {
49 | input.val(test.value).triggerHandler('input');
50 | expect(model.$viewValue).toBe(test.viewValue);
51 | expect(model.$modelValue).toBe(test.modelValue);
52 | });
53 | });
54 |
55 | it('should validate a phone number', function() {
56 | var input = TestUtil.compile('', {
57 | model: 30112
58 | });
59 | var model = input.controller('ngModel');
60 | expect(model.$valid).toBe(false);
61 | expect(model.$error.usPhoneNumber).toBe(true);
62 | input.val(3011201034).triggerHandler('input');
63 | expect(model.$valid).toBe(true);
64 | expect(model.$error.usPhoneNumber).toBeUndefined();
65 | });
66 |
67 | });
68 |
--------------------------------------------------------------------------------
/src/us/us-masks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var m = angular.module('ui.utils.masks.us', [])
4 | .directive('uiUsPhoneNumberMask', require('./phone/us-phone'));
5 |
6 | module.exports = m.name;
7 |
--------------------------------------------------------------------------------
/us.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./src/angular-input-masks.us');
4 |
--------------------------------------------------------------------------------
/us.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('angular-input-masks-standalone', function() {
4 | var moduleName = require('./us.js');
5 |
6 | beforeEach(angular.mock.module('ui.utils.masks'));
7 |
8 | it('should export the module name', function() {
9 | expect(moduleName).toBe('ui.utils.masks');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------