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

Commit 91aac4c

Browse files
committed
feat(validations): customisable validation triggers
1 parent ec98131 commit 91aac4c

File tree

15 files changed

+115
-85
lines changed

15 files changed

+115
-85
lines changed

dev/typescript/App.vue

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ import {
7979
required,
8080
email,
8181
pattern,
82+
ValidatorTrigger,
83+
ValidationTriggerTypes,
8284
} from '../../src';
8385
/* } from '../../dist/as-dynamic-forms.esm'; */
8486
export default defineComponent({
@@ -120,6 +122,7 @@ export default defineComponent({
120122
fields: {
121123
name: TextField({
122124
label: 'Name',
125+
customClass: 'w-1/2 pr-4',
123126
validations: [
124127
Validator({ validator: required, text: 'This field is required' }),
125128
],
@@ -130,6 +133,7 @@ export default defineComponent({
130133
customClass: {
131134
active: true,
132135
'text-blue': true,
136+
'w-1/2': true,
133137
},
134138
}),
135139
password: PasswordField({
@@ -139,6 +143,7 @@ export default defineComponent({
139143
}),
140144
stock: NumberField({
141145
label: 'Stock',
146+
customClass: 'w-1/2 pr-4',
142147
}),
143148
games: SelectField({
144149
label: 'Games',
@@ -161,7 +166,7 @@ export default defineComponent({
161166
}),
162167
console: SelectField({
163168
label: 'Console (Async Options)',
164-
customClass: 'w-1/2',
169+
customClass: 'w-1/2 pr-4',
165170
options: consoleOptions.value,
166171
}),
167172
steps: NumberField({
@@ -170,6 +175,7 @@ export default defineComponent({
170175
max: 60,
171176
step: 5,
172177
value: 5,
178+
customClass: 'w-1/2 ',
173179
}),
174180
awesomeness: CheckboxField({
175181
label: "Check if you're awesome",
@@ -205,10 +211,15 @@ export default defineComponent({
205211
}),
206212
customStyles: TextField({
207213
label: 'Custom Styles',
208-
required: true,
209214
customStyles: {
210215
border: '1px solid teal',
211216
},
217+
validations: [emailValidator],
218+
219+
validationTrigger: ValidatorTrigger({
220+
type: ValidationTriggerTypes.CHANGE,
221+
threshold: 4,
222+
}),
212223
}),
213224
readonly: TextField({
214225
label: 'Readonly',

dev/typescript/main.ts

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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/dynamic-input/DynamicInput.vue

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,6 @@ export default defineComponent({
9191
{
9292
'form-group--inline': props?.control?.type === FieldTypes.CHECKBOX,
9393
},
94-
{
95-
'form-group--success':
96-
props?.control?.valid &&
97-
props?.control?.dirty &&
98-
props?.control?.touched,
99-
},
100-
{
101-
'form-group--error': showErrors.value,
102-
},
10394
];
10495
10596
if (isArray(props?.control?.customClass)) {
@@ -118,17 +109,6 @@ export default defineComponent({
118109
() => props?.control?.touched && options?.autoValidate,
119110
);
120111
121-
const showErrors = computed(() => {
122-
return (
123-
props?.control?.errors &&
124-
keys(props?.control?.errors).length > 0 &&
125-
(props.submited || autoValidate.value)
126-
);
127-
/* props.control.errors &&
128-
Object.keys(props.control.errors).length > 0 &&
129-
(this.submited || this.autoValidate) */
130-
});
131-
132112
const errorMessages = computed(() => {
133113
const errors = values(props?.control?.errors || {});
134114
if (errors.length > 0 && (props.submited || autoValidate.value)) {

src/components/number-input/NumberInput.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default defineComponent({
1212
name: 'asNumberInput',
1313
props,
1414
setup(props, { emit }) {
15-
const { onChange, onFocus, onBlur } = useInputEvents(props, emit);
15+
const { onInput, onFocus, onBlur } = useInputEvents(props, emit);
1616
const { isRequired } = useInputValidation(props, emit);
1717
1818
return () =>
@@ -35,7 +35,7 @@ export default defineComponent({
3535
ariaRequired: isRequired.value,
3636
onFocus,
3737
onBlur,
38-
onChange,
38+
onInput,
3939
});
4040
},
4141
});

src/components/radio-input/RadioInput.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default defineComponent({
1111
name: 'asRadioInput',
1212
props,
1313
setup(props, { emit }) {
14-
const { onChange, onFocus, onBlur } = useInputEvents(props, emit);
14+
const { onInput, onFocus, onBlur } = useInputEvents(props, emit);
1515
const renderRadios = props?.control?.options?.map(option => {
1616
return h('div', { class: 'radio-input' }, [
1717
h('input', {
@@ -23,7 +23,7 @@ export default defineComponent({
2323
value: option.value,
2424
onFocus,
2525
onBlur,
26-
onChange,
26+
onInput,
2727
}),
2828
h(
2929
'label',

src/components/select-input/SelectInput.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default defineComponent({
1414
props,
1515
setup(props, { emit }) {
1616
return () => {
17-
const { onChange, onFocus, onBlur } = useInputEvents(props, emit);
17+
const { onInput, onFocus, onBlur } = useInputEvents(props, emit);
1818
const { isRequired } = useInputValidation(props, emit);
1919
2020
const formattedOptions = computed(() => {
@@ -43,7 +43,7 @@ export default defineComponent({
4343
ariaRequired: isRequired.value,
4444
onFocus,
4545
onBlur,
46-
onChange,
46+
onInput,
4747
},
4848
options,
4949
);

src/components/text-area-input/TextAreaInput.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default defineComponent({
1111
name: 'asTextAreaInput',
1212
props,
1313
setup(props, { emit }) {
14-
const { onChange, onFocus, onBlur } = useInputEvents(props, emit);
14+
const { onInput, onFocus, onBlur } = useInputEvents(props, emit);
1515
const { isRequired } = useInputValidation(props, emit);
1616
1717
return () =>
@@ -32,7 +32,7 @@ export default defineComponent({
3232
ariaRequired: isRequired.value,
3333
onFocus,
3434
onBlur,
35-
onChange,
35+
onInput,
3636
});
3737
},
3838
});

src/components/text-input/TextInput.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default defineComponent({
2121
name: 'asTextInput',
2222
props,
2323
setup(props, { emit }) {
24-
const { onChange, onFocus, onBlur, getClasses } = useInputEvents(
24+
const { onInput, onFocus, onBlur, getClasses } = useInputEvents(
2525
props,
2626
emit,
2727
);
@@ -44,7 +44,7 @@ export default defineComponent({
4444
ariaLabelledBy: props.control.ariaLabelledBy,
4545
onFocus,
4646
onBlur,
47-
onChange,
47+
onInput,
4848
});
4949
},
5050
});

src/composables/input-events.ts

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,50 @@
1-
import { computed, watch } from 'vue';
2-
import { hasValue, isArray, isObject } from '../core/utils/helpers';
1+
import { computed, ComputedRef, watch } from 'vue';
2+
import { hasValue } from '../core/utils/helpers';
33

44
import { useInputValidation } from '@/composables/use-validation';
5-
import { BindingObject } from '..';
5+
import { ValidationTriggerTypes } from '@/core/models';
66

7-
export function useInputEvents(props: any, emit: any) {
8-
const { validate } = useInputValidation(props, emit);
7+
interface InputEventsComposition {
8+
onInput: ($event: Event) => void;
9+
onCheck: ($event: Event) => void;
10+
onFocus: () => void;
11+
onBlur: () => void;
12+
getClasses: ComputedRef<(string | { [key: string]: boolean })[]>;
13+
}
14+
15+
export function useInputEvents(props: any, emit: any): InputEventsComposition {
16+
const { validate, getValidationClasses } = useInputValidation(props, emit);
17+
18+
function onInput($event: Event): void {
19+
const element = $event.target as HTMLInputElement;
920

10-
function onChange($event): void {
11-
if (props.control && hasValue($event.target.value)) {
21+
if (props.control && hasValue(element.value)) {
1222
$event.stopImmediatePropagation();
1323

14-
validate();
24+
if (
25+
(!props.control.valid &&
26+
props.control.validationTrigger.type ===
27+
ValidationTriggerTypes.BLUR) ||
28+
(props.control.validationTrigger.type ===
29+
ValidationTriggerTypes.CHANGE &&
30+
element.value.length >= props.control.validationTrigger.threshold)
31+
) {
32+
validate();
33+
}
1534
emit('change', {
1635
name: props.control.name,
17-
value: $event.target.value,
36+
value: element.value,
1837
});
1938
}
2039
}
21-
function onCheck($event): void {
40+
function onCheck($event: Event): void {
41+
const element = $event.target as HTMLInputElement;
2242
if (props.control) {
2343
$event.stopImmediatePropagation();
2444

2545
emit('change', {
2646
name: props.control.name,
27-
value: $event.target.checked,
47+
value: element.checked,
2848
});
2949
}
3050
}
@@ -33,18 +53,16 @@ export function useInputEvents(props: any, emit: any) {
3353
}
3454
function onBlur(): void {
3555
emit('blur', { name: props.control.name });
36-
validate();
37-
}
3856

39-
const getClasses = computed(() => {
40-
const classes = ['form-control'];
41-
if (isArray(props.control.customClass)) {
42-
return [...classes, ...(props.control.customClass as BindingObject[])];
43-
}
44-
if (isObject(props.control.customClass)) {
45-
return [...classes, props.control.customClass];
57+
if (props.control.validationTrigger.type === ValidationTriggerTypes.BLUR) {
58+
validate();
4659
}
47-
return [classes, props.control.customClass];
60+
}
61+
62+
const getClasses: ComputedRef<
63+
(string | { [key: string]: boolean })[]
64+
> = computed(() => {
65+
return ['form-control', ...getValidationClasses.value];
4866
});
4967

5068
watch(
@@ -61,7 +79,7 @@ export function useInputEvents(props: any, emit: any) {
6179

6280
return {
6381
onFocus,
64-
onChange,
82+
onInput,
6583
onBlur,
6684
onCheck,
6785
getClasses,

src/composables/use-validation.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@
33
import { computed } from 'vue';
44

55
export function useInputValidation(props: any, emit: any) {
6+
const isRequired = computed(() => {
7+
return props.control.validations.some(validation => validation.type === 'required')
8+
});
9+
10+
const requiresValidation = computed(() => {
11+
return props.control.validations.length > 0;
12+
});
13+
614
function validate(): void {
7-
if (props.control.touched || props.control.dirty) {
15+
if ((props.control.touched || props.control.dirty) && requiresValidation.value) {
816
const validation = props.control.validations.reduce((prev, curr) => {
917
const val =
1018
typeof curr.validator === 'function'
11-
? curr.validator(props.control)
19+
? curr.validator(props.control.value)
1220
: null;
1321
if (val !== null) {
1422
const [key, value] = Object.entries(val)[0];
@@ -34,11 +42,11 @@ export function useInputValidation(props: any, emit: any) {
3442
}
3543
}
3644

37-
3845
const getValidationClasses = computed(() => {
3946
return [
4047
{
4148
'form-control--success':
49+
requiresValidation.value &&
4250
props.control.valid &&
4351
props.control.dirty &&
4452
props.control.touched,
@@ -50,13 +58,10 @@ export function useInputValidation(props: any, emit: any) {
5058

5159
});
5260

53-
const isRequired = computed(() => {
54-
return props.control.validations.some(validation => validation.type === 'required')
55-
})
56-
5761
return {
5862
validate,
5963
getValidationClasses,
6064
isRequired,
65+
requiresValidation,
6166
};
6267
}

src/core/factories.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
CustomInput,
1313
FormControl,
1414
FormValidator,
15+
ValidationTriggerTypes,
16+
ValidationTrigger,
1517
} from './models';
1618

1719
const EMPTY_CONTROL = {
@@ -32,6 +34,10 @@ export const FieldBase = ({
3234
placeholder = null,
3335
autocomplete = null,
3436
readonly = false,
37+
validationTrigger = ValidatorTrigger({
38+
type: ValidationTriggerTypes.BLUR,
39+
threshold: 0,
40+
}),
3541
}: InputBase): InputBase =>
3642
({
3743
validations,
@@ -44,6 +50,7 @@ export const FieldBase = ({
4450
placeholder,
4551
autocomplete,
4652
readonly,
53+
validationTrigger,
4754
} as InputBase);
4855

4956
export const TextField = ({
@@ -156,3 +163,11 @@ export const Validator = ({
156163
validator,
157164
text,
158165
});
166+
167+
export const ValidatorTrigger = ({
168+
type,
169+
threshold,
170+
}: ValidationTrigger): ValidationTrigger => ({
171+
type,
172+
threshold,
173+
});

0 commit comments

Comments
 (0)