Skip to content
This repository was archived by the owner on Jun 27, 2023. It is now read-only.

Commit f3dc2dd

Browse files
authored
Merge pull request #198 from asigloo/feature/customizable-validation-behavior
Feature/customisable validation behavior
2 parents 484ad01 + 157d111 commit f3dc2dd

File tree

19 files changed

+577
-234
lines changed

19 files changed

+577
-234
lines changed

dev/typescript/App.vue

+36-11
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,9 @@
6262
</template>
6363

6464
<script lang="ts">
65-
import { mockAsync } from '@/core/utils/helpers';
65+
import { mockAsync, mockAsyncValidator } from '@/core/utils/helpers';
6666
import { computed, defineComponent, onMounted, reactive, ref } from 'vue';
6767
import {
68-
FormValidation,
69-
email,
70-
pattern,
7168
TextField,
7269
SelectField,
7370
EmailField,
@@ -77,6 +74,13 @@ import {
7774
RadioField,
7875
CustomField,
7976
ColorField,
77+
Validator,
78+
FormValidator,
79+
required,
80+
email,
81+
pattern,
82+
ValidatorTrigger,
83+
ValidationTriggerTypes,
8084
} from '../../src';
8185
/* } from '../../dist/as-dynamic-forms.esm'; */
8286
export default defineComponent({
@@ -85,18 +89,28 @@ export default defineComponent({
8589
const title = ref('Vue Dynamic Forms');
8690
const formValues = reactive({});
8791
let consoleOptions = ref();
88-
const emailValidator: FormValidation = {
92+
const emailValidator: FormValidator = {
8993
validator: email,
9094
text: 'Email format is incorrect',
9195
};
9296
93-
const passwordValidator: FormValidation = {
97+
const emailUniquenessValidator: FormValidator = {
98+
validator: value =>
99+
mockAsyncValidator(
100+
'isUnique',
101+
value === '[email protected]',
102+
2000,
103+
),
104+
text: 'Email must be unique',
105+
};
106+
107+
const passwordValidator: FormValidator = Validator({
94108
validator: pattern(
95109
'^(?=.*[a-z])(?=.*[A-Z])(?=.*)(?=.*[#$^+=!*()@%&]).{8,10}$',
96110
),
97111
text:
98112
'Password must contain at least 1 Uppercase, 1 Lowercase, 1 number, 1 special character and min 8 characters max 10',
99-
};
113+
});
100114
101115
const form = computed(() => ({
102116
id: 'example-form',
@@ -118,14 +132,18 @@ export default defineComponent({
118132
fields: {
119133
name: TextField({
120134
label: 'Name',
121-
required: true,
135+
customClass: 'w-1/2 pr-4',
136+
validations: [
137+
Validator({ validator: required, text: 'This field is required' }),
138+
],
122139
}),
123140
email: EmailField({
124141
label: 'Email',
125-
validations: [emailValidator],
142+
validations: [emailValidator, emailUniquenessValidator],
126143
customClass: {
127144
active: true,
128145
'text-blue': true,
146+
'w-1/2': true,
129147
},
130148
}),
131149
password: PasswordField({
@@ -135,6 +153,7 @@ export default defineComponent({
135153
}),
136154
stock: NumberField({
137155
label: 'Stock',
156+
customClass: 'w-1/2 pr-4',
138157
}),
139158
games: SelectField({
140159
label: 'Games',
@@ -157,7 +176,7 @@ export default defineComponent({
157176
}),
158177
console: SelectField({
159178
label: 'Console (Async Options)',
160-
customClass: 'w-1/2',
179+
customClass: 'w-1/2 pr-4',
161180
options: consoleOptions.value,
162181
}),
163182
steps: NumberField({
@@ -166,6 +185,7 @@ export default defineComponent({
166185
max: 60,
167186
step: 5,
168187
value: 5,
188+
customClass: 'w-1/2 ',
169189
}),
170190
awesomeness: CheckboxField({
171191
label: "Check if you're awesome",
@@ -201,10 +221,15 @@ export default defineComponent({
201221
}),
202222
customStyles: TextField({
203223
label: 'Custom Styles',
204-
required: true,
205224
customStyles: {
206225
border: '1px solid teal',
207226
},
227+
validations: [emailValidator],
228+
229+
validationTrigger: ValidatorTrigger({
230+
type: ValidationTriggerTypes.CHANGE,
231+
threshold: 4,
232+
}),
208233
}),
209234
readonly: TextField({
210235
label: 'Readonly',

dev/typescript/main.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const VueDynamicForms = createDynamicForms({
1313
customClass: 'plugin-options-class-added',
1414
customStyles: {
1515
display: 'flex',
16-
flexDirection: 'column',
16+
flexWrap: 'wrap',
1717
},
1818
method: 'POST',
1919
netlify: false,

dev/typescript/styles/base.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,600;0,700;1,400&family=Roboto:wght@300;500;700&display=swap');
22

33
.card {
4-
@apply max-w-lg w-full rounded-lg shadow-lg;
4+
@apply w-full rounded-lg shadow-lg;
55
}
66

77
.container {

src/components/checkbox-input/CheckboxInput.vue

+26-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { defineComponent, h, PropType } from 'vue';
33
import { FormControl, CheckboxInput } from '@/core/models';
44
import { useInputEvents } from '@/composables/input-events';
5+
import { useInputValidation } from '@/composables/use-validation';
56
67
const props = {
78
control: Object as PropType<FormControl<CheckboxInput>>,
@@ -12,15 +13,21 @@ export default defineComponent({
1213
props,
1314
setup(props, { emit }) {
1415
const { onCheck, onFocus, onBlur } = useInputEvents(props, emit);
16+
17+
const { errorMessages, isPendingValidation } = useInputValidation(
18+
props,
19+
emit,
20+
);
21+
1522
const renderCheckbox = [
1623
h('input', {
17-
name: props?.control?.name || '',
18-
type: props?.control?.type,
19-
id: props?.control?.name,
20-
disabled: props?.control?.disabled,
24+
name: props.control.name || '',
25+
type: props.control.type,
26+
id: props.control.name,
27+
disabled: props.control.disabled,
2128
class: ['checkbox-control'],
22-
value: props?.control?.value,
23-
checked: props?.control?.value,
29+
value: props.control.value,
30+
checked: props.control.value,
2431
onFocus,
2532
onBlur,
2633
onChange: onCheck,
@@ -29,9 +36,9 @@ export default defineComponent({
2936
'label',
3037
{
3138
class: ['checkbox-label'],
32-
for: props?.control?.name,
39+
for: props.control.name,
3340
},
34-
props?.control?.label,
41+
props.control.label,
3542
),
3643
];
3744
@@ -45,6 +52,17 @@ export default defineComponent({
4552
},
4653
renderCheckbox,
4754
),
55+
isPendingValidation.value
56+
? null
57+
: h(
58+
'div',
59+
{
60+
class: 'form-errors',
61+
},
62+
errorMessages.value.map(error =>
63+
h('p', { class: 'form-error' }, error),
64+
),
65+
),
4866
];
4967
},
5068
});

src/components/dynamic-form/DynamicForm.vue

+25-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
:submited="submited"
1515
@change="valueChange"
1616
@blur="onBlur"
17+
@validate="onValidate"
1718
>
1819
<template v-slot:customField="props">
1920
<div
@@ -50,7 +51,14 @@ import { diff } from 'deep-object-diff';
5051
5152
import DynamicInput from '../dynamic-input/DynamicInput.vue';
5253
53-
import { DynamicForm, FieldTypes, FormControl, InputType } from '@/core/models';
54+
import {
55+
DynamicForm,
56+
FieldTypes,
57+
FormControl,
58+
InputType,
59+
ValidationEvent,
60+
InputEvent,
61+
} from '@/core/models';
5462
import { dynamicFormsSymbol } from '@/useApi';
5563
import { deepClone, hasValue, removeEmpty } from '@/core/utils/helpers';
5664
@@ -194,20 +202,30 @@ export default defineComponent({
194202
if (updatedCtrl) {
195203
updatedCtrl.value = event.value as string;
196204
updatedCtrl.dirty = true;
197-
validateControl(updatedCtrl);
198205
}
199206
ctx.emit('change', formValues.value);
200207
}
201208
}
202209
203-
function onBlur(control: FormControl<InputType>) {
204-
const updatedCtrl = findControlByName(control.name);
210+
function onBlur({ name }: InputEvent) {
211+
const updatedCtrl = findControlByName(name);
205212
if (updatedCtrl) {
206213
updatedCtrl.touched = true;
207214
}
208215
}
209216
210-
function validateControl(control: FormControl<InputType>) {
217+
function onValidate({ name, errors, valid }: ValidationEvent) {
218+
const updatedCtrl = findControlByName(name);
219+
if (updatedCtrl) {
220+
updatedCtrl.errors = removeEmpty({
221+
...updatedCtrl.errors,
222+
...errors,
223+
});
224+
updatedCtrl.valid = valid;
225+
}
226+
}
227+
228+
/* function validateControl(control: FormControl<InputType>) {
211229
if (control.validations) {
212230
const validation = control.validations.reduce((prev, curr) => {
213231
const val =
@@ -233,7 +251,7 @@ export default defineComponent({
233251
control.errors = validation;
234252
control.valid = Object.keys(validation).length === 0;
235253
}
236-
}
254+
} */
237255
238256
function detectChanges(fields) {
239257
const changes = diff(cache, deepClone(fields));
@@ -288,6 +306,7 @@ export default defineComponent({
288306
submited,
289307
formattedOptions,
290308
onBlur,
309+
onValidate,
291310
};
292311
},
293312
});

src/components/dynamic-input/DynamicInput.vue

+12-33
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ import {
2424
InputType,
2525
BindingObject,
2626
FieldTypes,
27+
ValidationEvent,
28+
InputEvent,
2729
} from '@/core/models';
2830
29-
import { values, keys, isArray, isObject } from '@/core/utils/helpers';
31+
import { values, isArray, isObject } from '@/core/utils/helpers';
3032
import { useInputEvents } from '@/composables/input-events';
3133
import { dynamicFormsSymbol } from '@/useApi';
3234
@@ -72,8 +74,10 @@ export default defineComponent({
7274
control: props?.control,
7375
style: props?.control.customStyles,
7476
onChange: valueChange,
75-
onBlur: () => emit('blur', props.control),
76-
onFocus: () => emit('focus', props.control),
77+
onBlur: (e: InputEvent) => emit('blur', e),
78+
onFocus: (e: InputEvent) => emit('focus', e),
79+
onValidate: (validation: ValidationEvent) =>
80+
emit('validate', validation),
7781
};
7882
});
7983
@@ -87,15 +91,6 @@ export default defineComponent({
8791
{
8892
'form-group--inline': props?.control?.type === FieldTypes.CHECKBOX,
8993
},
90-
{
91-
'form-group--success':
92-
props?.control?.valid &&
93-
props?.control?.dirty &&
94-
props?.control?.touched,
95-
},
96-
{
97-
'form-group--error': showErrors.value,
98-
},
9994
];
10095
10196
if (isArray(props?.control?.customClass)) {
@@ -114,17 +109,6 @@ export default defineComponent({
114109
() => props?.control?.touched && options?.autoValidate,
115110
);
116111
117-
const showErrors = computed(() => {
118-
return (
119-
props?.control?.errors &&
120-
keys(props?.control?.errors).length > 0 &&
121-
(props.submited || autoValidate.value)
122-
);
123-
/* props.control.errors &&
124-
Object.keys(props.control.errors).length > 0 &&
125-
(this.submited || this.autoValidate) */
126-
});
127-
128112
const errorMessages = computed(() => {
129113
const errors = values(props?.control?.errors || {});
130114
if (errors.length > 0 && (props.submited || autoValidate.value)) {
@@ -241,20 +225,15 @@ export default defineComponent({
241225
},
242226
[
243227
`${props?.control?.label}`,
244-
props?.control?.required ? requiredStar : '',
228+
props?.control?.validations?.some(
229+
validator => validator.type === 'required',
230+
)
231+
? requiredStar
232+
: '',
245233
],
246234
)
247235
: null,
248236
component,
249-
h(
250-
'div',
251-
{
252-
class: 'form-errors',
253-
},
254-
errorMessages.value.map(error =>
255-
h('p', { class: 'form-error' }, error),
256-
),
257-
),
258237
],
259238
);
260239
};

0 commit comments

Comments
 (0)