Skip to content

Commit

Permalink
FIO-4242 updated input mask for TextField
Browse files Browse the repository at this point in the history
  • Loading branch information
HannaKurban committed Jan 11, 2024
1 parent 7b0eecd commit 78ccef8
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 16 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"fast-json-patch": "^3.1.1",
"fetch-ponyfill": "^7.1.0",
"idb": "^7.1.1",
"inputmask": "^5.0.8",
"ismobilejs": "^1.1.1",
"json-logic-js": "^2.0.2",
"jstimezonedetect": "^1.0.7",
Expand Down
14 changes: 8 additions & 6 deletions src/components/_classes/component/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -2570,11 +2570,13 @@ export default class Component extends Element {

const checkMask = (value) => {
if (typeof value === 'string') {
const placeholderChar = this.placeholderChar;
if (this.component.type !== 'textfield') {
const placeholderChar = this.placeholderChar;

value = conformToMask(value, this.defaultMask, { placeholderChar }).conformedValue;
if (!FormioUtils.matchInputMask(value, this.defaultMask)) {
value = '';
value = conformToMask(value, this.defaultMask, { placeholderChar }).conformedValue;
if (!FormioUtils.matchInputMask(value, this.defaultMask)) {
value = '';
}
}
}
else {
Expand Down Expand Up @@ -2684,11 +2686,11 @@ export default class Component extends Element {
const input = this.performInputMapping(this.refs.input[index]);
const valueMaskInput = this.refs.valueMaskInput;

if (valueMaskInput?.mask) {
if (valueMaskInput?.mask && valueMaskInput.mask.textMaskInputElement) {
valueMaskInput.mask.textMaskInputElement.update(value);
}

if (input.mask) {
if (input.mask && input.mask.textMaskInputElement) {
input.mask.textMaskInputElement.update(value);
}
else if (input.widget && input.widget.setValue) {
Expand Down
4 changes: 2 additions & 2 deletions src/components/_classes/multivalue/Multivalue.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export default class Multivalue extends Field {
if (this.refs.input && this.refs.input.length) {
this.refs.input.forEach((input) => {
if (input.mask) {
input.mask.destroy();
input.mask.destroy ? input.mask.destroy() : input.mask.remove();
}
if (input.widget) {
input.widget.destroy();
Expand All @@ -129,7 +129,7 @@ export default class Multivalue extends Field {
if (this.refs.mask && this.refs.mask.length) {
this.refs.mask.forEach((input) => {
if (input.mask) {
input.mask.destroy();
input.mask.destroy ? input.mask.destroy() : input.mask.remove();
}
});
}
Expand Down
70 changes: 67 additions & 3 deletions src/components/textfield/TextField.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Input from '../_classes/input/Input';
import { conformToMask } from '@formio/vanilla-text-mask';
import Inputmask from 'inputmask';
import * as FormioUtils from '../../utils/utils';
import _ from 'lodash';

Expand Down Expand Up @@ -167,8 +168,14 @@ export default class TextFieldComponent extends Input {
const maskInput = this.refs.select ? this.refs.select[index]: null;
const mask = this.getMaskPattern(value.maskName);
if (textInput && maskInput && mask) {
const placeholderChar = this.placeholderChar;
textInput.value = conformToMask(textValue, FormioUtils.getInputMask(mask), { placeholderChar }).conformedValue;
if (textInput.inputmask) {
this.setInputMask(textInput, mask);
textInput.inputmask.setValue(textValue);
}
else {
const placeholderChar = this.placeholderChar;
textInput.value = conformToMask(textValue, FormioUtils.getInputMask(mask), { placeholderChar }).conformedValue;
}
maskInput.value = value.maskName;
}
else {
Expand Down Expand Up @@ -205,7 +212,11 @@ export default class TextFieldComponent extends Input {
return this.unmaskValue(value, displayMask);
}

if (this.refs.valueMaskInput?.mask) {
if (displayMask && displayMask !== valueMask) {
return Inputmask.format(Inputmask.unmask(value, displayMask), valueMask);
}

if (this.refs.valueMaskInput?.mask && this.refs.valueMaskInput.mask.textMaskInputElement) {
this.refs.valueMaskInput.mask.textMaskInputElement.update(value);
return this.refs.valueMaskInput?.value;
}
Expand All @@ -219,6 +230,59 @@ export default class TextFieldComponent extends Input {
maskName: maskInput ? maskInput.value : undefined
};
}
checkInputMaskValue(inputMask) {
let valid = true;
const maskValues = _.values(inputMask.split('').reduce((acc, el, i, mask) => {
if (el === '{' || el === '}') {
if (mask[i+1] === '{' || mask[i+1] === '}') {
valid = false;
}
acc[el] = (acc[el] ?? 0) + 1;
}
return acc;
},{}));
if (maskValues[0] !== maskValues[1]) {
valid = false;
}
return valid;
}

setInputMask(input, inputMask, usePlaceholder) {
if (this.type !== 'textfield') {
super.setInputMask(input, inputMask, usePlaceholder);
return;
}

inputMask = inputMask || this.component.displayMask || this.component.inputMask;
const mask = FormioUtils.getInputMask(inputMask, this.placeholderChar);
this.defaultMask = mask;

if (input && inputMask) {
try {
//remove previous mask
if (input.mask) {
input.mask.remove();
}
if (this.checkInputMaskValue(inputMask)) {
input.mask = new Inputmask(inputMask, {
clearMaskOnLostFocus: !!this.component.placeholder,
showMaskOnHover: !this.component.placeholder,
placeholder: this.placeholderChar || '',
}).mask(input);
}
}
catch (e) {
console.warn(e);
}
if (mask.numeric) {
input.setAttribute('pattern', '\\d*');
}

if (this.component.placeholder) {
input.setAttribute('placeholder', this.component.placeholder);
}
}
}

isHtmlRenderMode() {
return super.isHtmlRenderMode() ||
Expand Down
68 changes: 63 additions & 5 deletions src/components/textfield/TextField.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,14 +423,72 @@ describe('TextField Component', () => {
testFormatting(values, values[values.length-1]);
});

it('Should allow dynamic syntax for input mask', (done) => {
const form = _.cloneDeep(comp6);
form.components[0].inputMask = 'aa-9{1,3}/9[99]';

const validValues = [
'',
'bB-77/555',
'bc-789/8',
'De-7/8',
'tr-81/888'
];

const invalidValues = [
'123',
'12-hh/789',
'dd-/893',
'he-538/',
'e1-77/790'
];

const testValidity = (values, valid, lastValue) => {
_.each(values, (value) => {
const element = document.createElement('div');

Formio.createForm(element, form).then(form => {
form.setPristine(false);

const component = form.getComponent('textField');
const changed = component.setValue(value);
const error = 'Text Field does not match the mask.';

if (value) {
assert.equal(changed, true, 'Should set value');
}

setTimeout(() => {
if (valid) {
assert.equal(!!component.error, false, 'Should not contain error');
}
else {
assert.equal(!!component.error, true, 'Should contain error');
assert.equal(component.error.message, error, 'Should contain error message');
assert.equal(component.element.classList.contains('has-error'), true, 'Should contain error class');
assert.equal(component.refs.messageContainer.textContent.trim(), error, 'Should show error');
}

if (_.isEqual(value, lastValue)) {
done();
}
}, 300);
}).catch(done);
});
};

testValidity(validValues, true);
testValidity(invalidValues, false, invalidValues[invalidValues.length-1]);
});

it('Should provide validation for alphabetic input mask after setting value', (done) => {
const form = _.cloneDeep(comp6);
form.components[0].inputMask = 'a/A/a-a:a.a,aa';

const validValues = [
'',
'b/V/r-y:d.d,as',
'b/b/r-y:d.d,as',
'b/B/r-y:d.d,as',
];

const invalidValues = [
Expand Down Expand Up @@ -513,7 +571,7 @@ describe('TextField Component', () => {

setTimeout(() => {
assert.equal(!!component.error, false, 'Should not contain error');
assert.equal(component.getValue(), 's/s/s-s:s.s,ss', 'Should set and format value');
assert.equal(component.getValue().toLowerCase(), 's/s/s-s:s.s,ss', 'Should set and format value');

if (_.isEqual(value, lastValue)) {
done();
Expand Down Expand Up @@ -638,10 +696,10 @@ describe('TextField Component', () => {
'46/34-yy',
'ye/56-op',
'We/56-op',
'te/56-Dp',
];

const invalidValues = [
'te/56-Dp',
'te/E6-pp',
'tdddde/E6-pp',
'te/E6',
Expand Down Expand Up @@ -695,7 +753,7 @@ describe('TextField Component', () => {

const values = [
{ value:'S67gf-+f34cfd', expected: 'S6/73-cf' },
{ value:'56DDDfdsf23,DDdsf', expected: '56/23-ds' },
{ value:'56DDDfdsf23,DDdsf', expected: '56/23-DD' },
{ value:'--fs344d.g234df', expected: 'fs/34-dg' },
{ value:'000000000g234df', expected: '00/00-gd' },
];
Expand Down Expand Up @@ -814,7 +872,7 @@ describe('TextField Component', () => {
const component = form.getComponent('textField');
const input = component.refs.input[0];

assert.equal(input.placeholder, '.._../..', 'Should set placeholder using the char setting');
assert.equal(input.inputmask.undoValue, '.._../..', 'Should set placeholder using the char setting');

const changed = component.setValue(value);
const error = 'Text Field does not match the mask.';
Expand Down
5 changes: 5 additions & 0 deletions src/validator/Validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
convertFormatToMoment, getArrayFromComponentPath, unescapeHTML
} from '../utils/utils';
import moment from 'moment';
import Inputmask from 'inputmask';
import fetchPonyfill from 'fetch-ponyfill';
const { fetch, Headers, Request } = fetchPonyfill({
Promise: Promise
Expand Down Expand Up @@ -694,6 +695,10 @@ class ValidationChecker {
inputMask = setting;
}

if (value && inputMask && typeof value === 'string' && component.type === 'textfield' ) {
return Inputmask.isValid(value, inputMask);
}

inputMask = inputMask ? getInputMask(inputMask) : null;

if (value && inputMask && !component.skipMaskValidation) {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4366,6 +4366,11 @@ ini@^1.3.4:
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==

inputmask@^5.0.8:
version "5.0.8"
resolved "https://registry.yarnpkg.com/inputmask/-/inputmask-5.0.8.tgz#cd0f70b058c3291a0d4f27de25dbfc179c998bb4"
integrity sha512-1WcbyudPTXP1B28ozWWyFa6QRIUG4KiLoyR6LFHlpT4OfTzRqFfWgHFadNvRuMN1S9XNVz9CdNvCGjJi+uAMqQ==

inquirer@^7.0.0:
version "7.3.3"
resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003"
Expand Down

0 comments on commit 78ccef8

Please sign in to comment.