From 249c042be73dfbacca8ba1fde14aa2122fffc219 Mon Sep 17 00:00:00 2001 From: Ben-hur Santos Ott Date: Mon, 22 May 2017 16:13:21 -0300 Subject: [PATCH] feat(custom): adding new engine to custom mask --- README.md | 100 ++++++++++++---- __tests__/custom.mask.test.js | 53 ++++++++- ...bers.mask.js => only-numbers.mask.test.js} | 2 +- lib/masks/custom.mask.js | 111 +++++++++++++----- package.json | 2 +- 5 files changed, 212 insertions(+), 56 deletions(-) rename __tests__/{only-numbers.mask.js => only-numbers.mask.test.js} (99%) diff --git a/README.md b/README.md index 9f95c44e..ef9c7a29 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ export default class MyComponent extends Component { ``` ### Props -**type** + +#### type *credit-card*: use the mask 9999 9999 9999 9999. It accepts options (see later in this doc).
*cpf*: use the mask `999.999.999-99` and `numeric` keyboard.
@@ -64,7 +65,7 @@ export default class MyComponent extends Component { *custom*: use your custom mask (see the options props later in this doc).
-**onChangeText** +#### onChangeText Invoked after new value applied to mask. ```jsx @@ -81,7 +82,7 @@ onChangeText(text) { ``` -**checkText** +#### checkText Allow you to check and prevent value to be inputed. @@ -100,7 +101,8 @@ checkText(previous, next) { checkText={this.checkText.bind(this)} /> ``` -**customTextInput**
+#### customTextInput + You can use this prop if you want custom text input instead native TextInput component: ```jsx @@ -118,14 +120,16 @@ const Textfield = MKTextField.textfield() placeholder="Enter text to see events" /> ``` -**TextInput Props**
+#### TextInput Props + You can use the native props of TextInput, with this in mind: * onChangeText is intercepted by component. * value is intercepted by component. * if you pass keyboardType, it will override the keyboardType of masked component. -**TextInput Methods**
+#### TextInput Methods + If you want to use the methods of the native TextInput, use the `getElement()` method: ```jsx @@ -154,11 +158,12 @@ export default class App extends React.Component { } ``` -**Options**
+#### Options + Some types accept options, use it like this: `` -For `type={'money'}`
+**For `type={'money'}`**
* *options={...}* * `precision` (Number, default 2): the decimal places. * `separator` (String, default ','): the decimal separator. @@ -167,12 +172,12 @@ For `type={'money'}`
* `suffixUnit` (String, default ''): the suffix text. * `zeroCents` (Boolean, default false): if must show cents. -For `type={'cel-phone'}`
+**For `type={'cel-phone'}`**
* *options={...}* * `withDDD` (Boolean, default true): if the ddd will be include in the mask. * `dddMask` (String, default '(99) '): the default mask applied if `withDDD` is true. -For `type={'datetime'}`
+**For `type={'datetime'}`**
* *options={...}* * `format` (String, default DD/MM/YYYY HH:mm:ss): moment date format. It accepts the following: * DD/MM/YYYY HH:mm:ss @@ -184,22 +189,64 @@ For `type={'datetime'}`
* HH * *You can use all of dates with `-` instead of `/` if you want* -For `type={'custom'}`
+**For `type={'custom'}`**
* *options={...}* - * `mask` (String, default ''): your mask template. - * `9`: accept digit. - * `A`: accept alpha. - * `S`: accept alphanumeric. - * `validator` (Function, default returns true): the function use to validate value. Must return true if valid or false if invalid. - * The function receives 2 parameters: - * `value`: current value. - * `settings`: current settings - * `getRawValue` (Function, default returns the current value): the function that's invoked when `getRawValue` method from component is called. - * The function receives 2 parameters: - * `value`: current value. - * `settings`: current settings - -For `type={'credit-card'}`
+ +```jsx +{ + /** + * mask: (String | required | default '') + * the mask pattern + * 9 - accept digit. + * A - accept alpha. + * S - accept alphanumeric. + * * - accept all. + */ + mask: '999#AAA', + + /** + * validator: (Function | optional | defaults returns true) + * use this funcion to inform if the inputed value is a valid value (for invalid phone numbers, for example). The isValid method use this validator. + */ + validator: function(value, settings) { + return true + }, + /** + * getRawValue: (Function | optional | defaults return current masked value) + * use this function to parse and return values to use what you want. + * for example, if you want to create a phone number mask (999) 999-99-99 but want to get only + * the numbers for value, use this method for this parse step. + */ + getRawValue: function(value, settings) { + return 'my converted value'; + }, + /** + * translation: (Object | optional | defaults 9, A, S, *) + * the dictionary that translate mask and value. + * you can change defaults by simple override the key (9, A, S, *) or create some new. + */ + translation: { + // this is a custom translation. The others (9, A, S, *) still works. + // this translation will be merged and turns into 9, A, S, *, #. + '#': function(val) { + if (val === ' ') { + return val; + } + + // if returns null, undefined or '' (empty string), the value will be ignored. + return null; + }, + // in this case, we will override build-in * translation (allow all characters) + // and set this to allow only blank spaces and some special characters. + '*': function(val) { + return [' ', '#', ',', '.', '!'].indexOf(val) >= 0 ? val : null; + } + } +} + +``` + +**For `type={'credit-card'}`**
* *options={...}* * `obfuscated` (Boolean, default false): if the mask must be `9999 **** **** 9999` @@ -302,6 +349,9 @@ var money = MaskService.toMask('money', '123', { # Changelog +## 1.5.0 +* Adding new and powerfull `custom` engine mask \m/. + ## 1.4.0 * Adding `customTextInput` to allow other inputs instead native TextInput. (thank to [Hellon Canella](https://github.com/helloncanella)) diff --git a/__tests__/custom.mask.test.js b/__tests__/custom.mask.test.js index 9fdcd98c..3f0d0869 100644 --- a/__tests__/custom.mask.test.js +++ b/__tests__/custom.mask.test.js @@ -130,4 +130,55 @@ test('123 with mask 999 results 123 and raw value 123(type Number)', () => { expect(received).toBe(expected); expect(receivedRawValue).toBe(expectedRawValue); -}); \ No newline at end of file +}); + +test('mask with custom translation and match', () => { + var mask = new CustomMask(); + var options = { + mask: '999&AAA', + translation: { + '&': function(val) { + return ['#', '.', ':'].indexOf(val) >= 0 ? val : null; + } + } + } + + var expected = '123#ABC'; + var received = mask.getValue('123#ABC', options); + + expect(received).toBe(expected); +}); + +test('mask with custom translation and not match', () => { + var mask = new CustomMask(); + var options = { + mask: '999&AAA', + translation: { + '&': function(val) { + return ['#', '.', ':'].indexOf(val) >= 0 ? val : null; + } + } + } + + var expected = '123'; + var received = mask.getValue('123|ABC', options); + + expect(received).toBe(expected); +}); + +test('mask with custom translation and optionals and matching', () => { + var mask = new CustomMask(); + var options = { + mask: '999***AAA&', + translation: { + '&': function(val) { + return ['#', '.', ':'].indexOf(val) >= 0 ? val : null; + } + } + } + + var expected = '123|% ABC.'; + var received = mask.getValue('123|% ABC.', options); + + expect(received).toBe(expected); +}); diff --git a/__tests__/only-numbers.mask.js b/__tests__/only-numbers.mask.test.js similarity index 99% rename from __tests__/only-numbers.mask.js rename to __tests__/only-numbers.mask.test.js index 798de3df..88d62d49 100644 --- a/__tests__/only-numbers.mask.js +++ b/__tests__/only-numbers.mask.test.js @@ -41,4 +41,4 @@ test('1 results 1 and raw value 1', () => { expect(received).toBe(expected); expect(receivedRawValue).toBe(expectedRawValue); -}); \ No newline at end of file +}); diff --git a/lib/masks/custom.mask.js b/lib/masks/custom.mask.js index 7471c048..10b2e813 100644 --- a/lib/masks/custom.mask.js +++ b/lib/masks/custom.mask.js @@ -1,31 +1,86 @@ import BaseMask from './_base.mask'; +const DEFAULT_TRANSLATION = { + '9': function (val) { + return val.replace(/[^0-9]+/g, ''); + }, + 'A': function (val) { + return val.replace(/[^a-zA-Z]+/g, ''); + }, + 'S': function (val) { + return val.replace(/[^a-zA-Z0-9]+/g, ''); + }, + '*': function (val) { + return val + } +} + +var invalidValues = [null, undefined, '']; + export default class CustomMask extends BaseMask { - static getType() { - return 'custom'; - } - - getKeyboardType() { - return "default"; - } - - getValue(value, settings) { - return this.getVMasker().toPattern(value, settings.mask); - } - - getRawValue(maskedValue, settings) { - if(!!settings && settings.getRawValue) { - return settings.getRawValue(maskedValue, settings); - } - - return maskedValue; - } - - validate(value, settings) { - if(!!settings && settings.validator) { - return settings.validator(value, settings); - } - - return true; - } -} \ No newline at end of file + static getType() { + return 'custom'; + } + + getKeyboardType() { + return "default"; + } + + getValue(value, settings) { + let { mask } = settings; + let translation = this.mergeSettings(DEFAULT_TRANSLATION, settings.translation); + + var result = ''; + + const maskSize = mask.length; + const valueSize = value.length; + + var maskResolved = 0; + var valueResolved = 0; + + while (maskResolved < maskSize && valueResolved < valueSize) { + const valueChar = value[valueResolved]; + const maskChar = mask[maskResolved]; + + if (valueChar === maskChar) { + result += valueChar; + maskResolved++; + valueResolved++; + continue; + } + + const handler = translation[maskChar]; + + if (!handler) { + result += maskChar; + maskResolved++; + continue; + } + + var masked = handler(valueChar); + if (invalidValues.indexOf(masked) < 0) { + result += masked + maskResolved++ + } + valueResolved++ + } + + return result; + } + + getRawValue(maskedValue, settings) { + if (!!settings && settings.getRawValue) { + return settings.getRawValue(maskedValue, settings); + } + + return maskedValue; + } + + validate(value, settings) { + if (!!settings && settings.validator) { + return settings.validator(value, settings); + } + + return true; + } +} diff --git a/package.json b/package.json index e3360213..871ed255 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-masked-text", - "version": "1.4.0", + "version": "1.5.0", "description": "Text and TextInput with mask for React Native applications", "main": "index.js", "scripts": {