├── .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 [![Build Status](https://travis-ci.org/assisrafael/angular-input-masks.svg?branch=master)](https://travis-ci.org/assisrafael/angular-input-masks) [![Coverage Status](https://coveralls.io/repos/assisrafael/angular-input-masks/badge.svg?branch=master)](https://coveralls.io/r/assisrafael/angular-input-masks?branch=master) [![Standard Version](https://img.shields.io/badge/release-standard%20version-brightgreen.svg)](https://github.com/conventional-changelog/standard-version) 2 | 3 | [![NPM](https://nodei.co/npm/angular-input-masks.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/angular-input-masks/) 4 | 5 | [![Join the chat at https://gitter.im/assisrafael/angular-input-masks](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/assisrafael/angular-input-masks?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Bountysource](https://www.bountysource.com/badge/team?team_id=60791&style=bounties_posted)](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 |
18 |

ui-fr-phone-number

19 |
20 | {{phoneNumber}} - {{form.phoneNumberTest.$valid}}
21 |
22 |
23 | {{initializedPhoneNumber}} - {{form.initializedPhoneNumberTest.$valid}}
24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /demo/us.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Mask US Demo 6 | 7 | 8 | 9 | 15 | 16 | 17 |
18 |

ui-us-phone-number

19 |
20 | {{phoneNumber}} - {{form.phoneNumberTest.$valid}}
21 |
22 |
23 | {{initializedPhoneNumber}} - {{form.initializedPhoneNumberTest.$valid}}
24 |
25 |
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 |
18 |

ui-br-car-plate-mask

19 |
20 | - {{form.field17.$error}}
21 |
22 |
23 | - {{form.field18.$error}}
24 |
25 |
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 |
18 |

ui-br-cep-mask

19 |
20 | - {{form.field17.$error}}
21 |
22 |
23 | - {{form.field18.$error}}
24 |
25 |
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 |
18 |

ui-br-cnpj-mask

19 |
20 | - {{form.field9.$error}}
21 |
22 | - {{form.field10.$error}}
23 |
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 |
19 |

ui-br-cpfcnpj-mask

20 |
21 | - {{form.field11.$error}}
22 |
23 | - {{form.field12.$error}}
24 |
25 | - {{form.field13.$error}}
26 |
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 |
18 |

ui-br-cpf-mask

19 |
20 | - {{form.field7.$error}}
21 |
22 | - {{form.field8.$error}}
23 |
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 |
23 |

ui-br-ie-mask

24 |
25 | MG: -
26 |
27 | 28 |
29 | : -
30 |
31 | 34 |
35 | : -
36 |
37 |
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 |
23 |

ui-nfe-access-key-mask

24 | 25 |
26 | ModelValue: 27 |
28 | Errors: 29 |
30 |
31 | 32 |
33 | ModelValue: 34 |
35 | Errors: 36 |
37 |
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 |
18 |

ui-br-numero-beneficio-mask

19 |
20 | - {{form.field7.$error}}
21 |
22 | - {{form.field8.$error}}
23 |
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 |
18 |

ui-br-phone-number-mask

19 |
20 | - - {{form.freeLinePhoneNumber.$error}}
21 |
22 |
23 | - - {{form.phoneNumber.$error}}
24 |
25 |
26 | - - {{form.initializedPhoneNumber.$error}}
27 |
28 |
29 | - - {{form.simplePhoneNumber.$error}}
30 |
31 |
32 | - - {{form.areaCodePhoneNumber.$error}}
33 |
34 |
35 | - - {{form.countryCodePhoneNumber.$error}}
36 |
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 |
18 |

ui-ch-phone-number-mask

19 |
20 | - - {{form.freeLinePhoneNumber.$error}}
21 |
22 |
23 | - - {{form.phoneNumber.$error}}
24 |
25 |
26 | - - {{form.initializedPhoneNumber.$error}}
27 |
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 |
18 |

ui-fr-phone-number-mask

19 |
20 | {{phoneNumber}} - {{form.phoneNumberTest.$valid}} 21 | 22 |
23 |
24 | 25 |
26 | {{initializedPhoneNumber}} - {{form.initializedPhoneNumberTest.$valid}}
27 |
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 |
18 |

ui-br-phone-number

19 |
20 | - - {{form.freeLinePhoneNumber.$error}}
21 |
22 |
23 | - - {{form.creditCard.$error}}
24 |
25 |
26 | - - {{form.initializedCC.$error}}
27 |
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 |
20 |

ui-date-mask

21 | 22 |
23 | ModelValue: 24 |
25 | Errors: 26 |
27 |
28 | 29 |
30 | ModelValue: 31 |
32 | Errors: 33 |
34 | 35 |
36 | ModelValue: 37 |
38 | Errors: 39 |
40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /src/global/date/date.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Date Spec 6 | 7 | 8 | 9 | 16 | 17 | 18 |
19 |

ui-date-mask

20 | 21 |
22 | ModelValue: 23 |
24 | Errors: 25 |
26 |
27 | 28 |
29 | ModelValue: 30 |
31 | Errors: 32 |
33 | 34 |
35 | ModelValue: 36 |
37 | Errors: 38 |
39 |
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 |
21 |

ui-money-mask

22 |
23 | - {{form.field14.$error}}
24 |
25 | - {{form.moneyField.$error}}
26 |
27 | - {{form.moneyField2.$error}}
28 |
29 | - {{form.field15.$error}}
30 |
31 | {{field16}} - {{form.field16.$error}} 32 |
33 | Money: 34 | Decimals:
35 | - {{form.field27.$error}} 36 |

ui-money-mask without space after currency symbol

37 | Money: 38 |

ui-money-mask with currency after value

39 | Money: 40 |
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 |
19 |

ui-number-mask

20 |
21 | - {{form.field21.$error}} 22 |
23 |
24 | 25 |
26 |
27 | - (accepts negative numbers. Press '-' to test) 28 |
29 |
30 | {{form.field3.$error}} 31 |
32 |
33 | - {{form.field4.$error}} 34 |
35 | Number: 36 | Decimals:
37 | - {{form.field23.$error}} 38 |
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 |
19 |

ui-percentage-mask

20 | $viewValue -
21 | $modelValue - 22 |
23 | 24 |
25 | - {{form.field6.$error}} 26 |
27 | 28 |
29 | - {{form.field60.$error}} 30 |
31 | 32 | Percentage: 33 | Decimals:
34 | - {{form.field25.$error}} 35 |
36 | 37 |

ui-percentage-mask with ui-negative-number

38 | $viewValue -
39 | $modelValue - 40 |
41 | 42 |

ui-percentage-value

43 | $viewValue -
44 | $modelValue - 45 | 46 |

ui-percentage-mask without space before %

47 | $viewValue -
48 |
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 |
20 |

ui-scientific-notation-mask

21 | 22 |
23 | ModelValue: 24 |
25 | Errors: 26 |
27 |
28 | 29 |
30 | ModelValue: 31 |
32 | Errors: 33 |
34 |
35 | 36 |
37 | ModelValue: 38 |
39 | Errors: 40 |
41 | 42 |
43 | ModelValue: 44 |
45 | Errors: 46 |
47 |
48 |
49 | - (accepts negative numbers. Press '-' to test) 50 |
51 |
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 |
19 |

ui-time-mask

20 |

default mode ("HH:MM:SS")

21 | 22 |
23 | ModelValue: 24 |
25 | Errors: 26 |
27 |
28 | 29 |
30 | ModelValue: 31 |
32 | Errors: 33 |
34 | 35 |

short mode ("HH:MM")

36 | 37 |
38 | ModelValue: 39 |
40 | Errors: 41 |
42 |
43 | 44 |
45 | ModelValue: 46 |
47 | Errors: 48 |
49 |
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 |
18 |

ui-us-phone-number-mask

19 |
20 | {{phoneNumber}} - {{form.phoneNumberTest.$valid}}
21 |
22 |
23 | {{initializedPhoneNumber}} - {{form.initializedPhoneNumberTest.$valid}}
24 |
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 | --------------------------------------------------------------------------------