Skip to content

Commit 5f46aa4

Browse files
reworking form so the validation works with the header items navigation
1 parent f479fcd commit 5f46aa4

13 files changed

Lines changed: 531 additions & 382 deletions

File tree

cypress/templates/testData.ts

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@ const defaultFields = {
5656
label: 'First Name',
5757
name: 'firstName',
5858
type: 'text' as const,
59+
required: true,
5960
},
6061
lastName: {
6162
label: 'Last Name',
6263
name: 'lastName',
6364
type: 'text' as const,
65+
required: true,
6466
},
6567
email: {
6668
label: 'Email',
@@ -159,6 +161,7 @@ const defaultFields = {
159161
label: 'Checkbox Single',
160162
name: 'isThisBoxChecked',
161163
type: 'checkbox' as const,
164+
trueValue: 'yes',
162165
required: true,
163166
},
164167
checkboxMultiple: {
@@ -204,6 +207,7 @@ const defaultFields = {
204207
},
205208
],
206209
type: 'radio' as const,
210+
trueValue: 'yes',
207211
required: true,
208212
},
209213
switch: {
@@ -329,7 +333,7 @@ const finalAnswer = {
329333
firstName: 'Bunny',
330334
lastName: 'Rabbit',
331335
email: 'test@test.com',
332-
password: 'password1',
336+
password: '7#L%W^7FC*8W#Xt7LvZCHU$%^Sxn&*PjUo',
333337
phone: '555-555-5555',
334338
url: 'https://test.com',
335339
number: '100',
@@ -340,8 +344,12 @@ const finalAnswer = {
340344

341345
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Less Common Fields Page //
342346
autocompleteAnimal: 'rabbit',
343-
autoCompleteMultipleAnimals: ['rabbit', 'duck'],
344-
combobox: [{ title: "Rabbit", value: "rabbit" }, { title: "Duck", value: "duck" }],
347+
348+
// ! Autocomplete with multiple and combobox not working correctly //
349+
// autoCompleteMultipleAnimals: ['rabbit', 'duck'],
350+
// combobox: [{ title: "Rabbit", value: "rabbit" }, { title: "Duck", value: "duck" }],
351+
352+
345353
color: '#804040',
346354
// date: 'Wed May 25 1977 00:00:00 GMT-0700 (Pacific Daylight Time)',
347355
// date: new Date('05/25/1977'),
@@ -353,6 +361,52 @@ const finalAnswer = {
353361
switchQuestion: 'yes',
354362
};
355363

364+
const newValidationSchema = [
365+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Common Fields Page //
366+
yupObject({
367+
firstName: yupString().required(isRequired('First Name')),
368+
lastName: yupString().required(isRequired('Last Name')),
369+
email: yupString().email('Must be a valid Email').required(isRequired('Email')),
370+
password: yupString().required(isRequired('Password'))
371+
.min(5, 'Password must have at least ${min} characters'),
372+
phone: yupString().required(isRequired('Phone')),
373+
url: yupString().required(isRequired('URL'))
374+
.url('Must be a valid URL'),
375+
number: yupNumber().required(isRequired('Number'))
376+
.min(Number(finalAnswer.number), 'Number must be at least ${min}'),
377+
description: yupString().required(isRequired('Description')),
378+
selectAnimal: yupString().required(isRequired('Select Animal')),
379+
selectsMultipleAnimals: yupArray().required(isRequired('Select Multiple Animals')),
380+
}),
381+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Less Common Fields Page //
382+
yupObject({
383+
autocompleteAnimal: yupString().required(isRequired('Autocomplete Animal')),
384+
385+
// ! Autocomplete with multiple and combobox not working correctly //
386+
// autoCompleteMultipleAnimals: yupArray().required(isRequired('Autocomplete Multiple Animal')),
387+
// combobox: yupArray().required(isRequired('Combobox'))
388+
// .min(2, 'Must select at least ${min} options'),
389+
390+
color: yupString().required(isRequired('Color')),
391+
// date: yupString().required(isRequired('Date')),
392+
}),
393+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Radio/Checkbox/Switch Fields Page //
394+
yupObject({
395+
isThisBoxChecked: yupString().required(isRequired('Checkbox Single')),
396+
checkboxMultiple: yupArray().required(isRequired('Checkbox Multiple'))
397+
.min(2, 'Must select at least ${min} options'),
398+
isSingleRadioSelected: yupString().required(isRequired('Radio Single'))
399+
.matches(/(yes|no)/, 'Only "yes" or "no" is allowed'),
400+
switchQuestion: yupString().required(isRequired('Switch Question'))
401+
.matches(/(yes)/, 'Only "yes" is allowed'),
402+
403+
// buttonField: yupArray().required(isRequired('Button Field')),
404+
// buttonField: yupString().required(isRequired('Button Field')).matches(/(yes|no)/, 'Only "yes" or "no" is allowed'),
405+
// .matches(/(^true)/, isRequired('Checkbox Single')),
406+
// .matches(/(^false)/, 'Checkbox must be not false'),
407+
}),
408+
];
409+
356410
const validationSchema = yupObject({
357411
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Common Fields Page //
358412
firstName: yupString().required(isRequired('First Name')),
@@ -371,9 +425,12 @@ const validationSchema = yupObject({
371425

372426
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Less Common Fields Page //
373427
autocompleteAnimal: yupString().required(isRequired('Autocomplete Animal')),
374-
autoCompleteMultipleAnimals: yupArray().required(isRequired('Autocomplete Multiple Animal')),
375-
combobox: yupArray().required(isRequired('Combobox'))
376-
.min(2, 'Must select at least ${min} options'),
428+
429+
// ! Autocomplete with multiple and combobox not working correctly //
430+
// autoCompleteMultipleAnimals: yupArray().required(isRequired('Autocomplete Multiple Animal')),
431+
// combobox: yupArray().required(isRequired('Combobox'))
432+
// .min(2, 'Must select at least ${min} options'),
433+
377434
color: yupString().required(isRequired('Color')),
378435
// date: yupString().required(isRequired('Date')),
379436

@@ -652,6 +709,7 @@ export {
652709
finalAnswer,
653710
items,
654711
navigationTest,
712+
newValidationSchema,
655713
stepperProps,
656714
validationSchema,
657715
};

src/plugin/VStepperForm.vue

Lines changed: 67 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
:error="currentPageHasErrors && errorPageIndexes.includes(i)"
4444
:title="page.title"
4545
:value="getIndex(i)"
46+
@click="headerCheckPreviousStepValidation(i)"
4647
>
4748
<v-tooltip
4849
v-if="!mobile && settings.headerTooltips && page?.fields && page?.fields.length > 0"
@@ -66,12 +67,8 @@
6667
</template>
6768
</v-stepper-header>
6869

69-
<Form
70+
<form
7071
ref="stepperFormRef"
71-
v-slot="{ validate }"
72-
:keep-values-on-unmount="settings?.keepValuesOnUnmount"
73-
:validate-on-mount="settings?.validateOnMount"
74-
:validation-schema="validationSchema"
7572
@submit="onSubmit"
7673
>
7774
<v-stepper-window>
@@ -84,6 +81,7 @@
8481
:value="getIndex(i)"
8582
>
8683
<v-container>
84+
8785
<PageContainer
8886
v-if="!page.isSummary"
8987
:key="`${getIndex(i)}-page`"
@@ -114,8 +112,8 @@
114112
:settings="settings"
115113
:summaryColumns="settings?.summaryColumns"
116114
@goToQuestion="stepperModel = $event"
117-
@submit="onSubmit(modelValue)"
118115
/>
116+
119117
</v-container>
120118
</v-stepper-window-item>
121119
</v-stepper-window>
@@ -129,7 +127,7 @@
129127
:disabled="nextButtonDisabled"
130128
:size="navButtonSize"
131129
:variant="navButtonVariant"
132-
@click="runValidation(validate, 'next', next)"
130+
@click="runValidation('next', next)"
133131
/>
134132
<v-btn
135133
v-else
@@ -139,6 +137,7 @@
139137
:size="navButtonSize"
140138
type="submit"
141139
:variant="navButtonVariant"
140+
@click="onSubmit"
142141
>Submit</v-btn>
143142
</template>
144143

@@ -153,7 +152,7 @@
153152
/>
154153
</template>
155154
</v-stepper-actions>
156-
</Form>
155+
</form>
157156
</template>
158157
</v-stepper>
159158
</v-container>
@@ -165,7 +164,7 @@
165164
// import { VStepper } from 'vuetify/components';
166165
// import { VStepperVertical } from 'vuetify/labs/VStepperVertical';
167166
import { watchDeep } from '@vueuse/core';
168-
import { Form } from 'vee-validate';
167+
import { useForm } from 'vee-validate';
169168
import { useDisplay } from 'vuetify';
170169
import type {
171170
ComputedClasses,
@@ -268,6 +267,8 @@ watchDeep(modelValue, () => {
268267
269268
const stepperModel = ref(1);
270269
270+
const currentPageIdx = computed(() => stepperModel.value - 1);
271+
271272
272273
const { mobile, sm } = useDisplay();
273274
const transitionComputed: ComputedRef<Props['transition']> = computed(() => stepperProps.transition);
@@ -295,33 +296,24 @@ const nextButtonDisabled = computed(() => {
295296
});
296297
297298
const prevButtonDisabled = computed(() => {
298-
const currentPage = stepperModel.value - 1;
299-
const { firstNonEditableIndex, lastNonEditableIndex } = useGetFirstAndLastEditableFalse(computedPages.value);
300-
// console.log('currentPage', currentPage);
301-
// console.log('firstNonEditableIndex', firstNonEditableIndex);
302-
// console.log('lastNonEditableIndex', lastNonEditableIndex);
303-
// console.log('isSummary', computedPages.value[currentPage]?.isSummary);
299+
const { lastNonEditableIndex } = useGetFirstAndLastEditableFalse(computedPages.value);
304300
305301
// First Page //
306-
if (currentPage === 0) {
307-
// console.log('FIRST PAGE');
302+
if (currentPageIdx.value === 0) {
308303
return true;
309304
}
310305
311306
// Entire Stepper Form is not editable //
312307
if (!stepperFormIsEditable.value) {
313-
// console.log('NOT EDITABLE');
314308
return true;
315309
}
316310
317311
// If current page has errors disable //
318312
if (currentPageHasErrors.value) {
319-
// console.log('HAS ERRORS');
320313
return true;
321314
}
322315
323-
if (currentPage - 1 === lastNonEditableIndex) {
324-
// console.log('LAST NON EDITABLE');
316+
if (currentPageIdx.value - 1 === lastNonEditableIndex) {
325317
return true;
326318
}
327319
@@ -379,9 +371,8 @@ function headerItemEnabled(page: Page): boolean {
379371
const pageIdx = currentPages.findIndex((p) => p === page);
380372
const pageEditable = page.editable !== false;
381373
const pageNotEditable = page.editable === false;
382-
const currentPageIdx = stepperModel.value - 1;
383-
const currentPageEditable = currentPages[currentPageIdx]?.editable !== false;
384-
// const currentPageNotEditable = currentPages[currentPageIdx]?.editable === false;
374+
const currentPageEditable = currentPages[currentPageIdx.value]?.editable !== false;
375+
// const currentPageNotEditable = currentPages[currentPageIdx.value]?.editable === false;
385376
const lastPageIdx = currentPages.length - 1;
386377
387378
const previousPageIdx = pageIdx - 1;
@@ -397,7 +388,7 @@ function headerItemEnabled(page: Page): boolean {
397388
// if (debug) {
398389
// console.groupCollapsed('page', page.title);
399390
// console.log('previousPageIdx\t', previousPageIdx);
400-
// console.log('currentPageIdx\t', currentPageIdx);
391+
// console.log('currentPageIdx\t', currentPageIdx.value);
401392
// console.log('nextPageIdx\t\t', nextPageIdx);
402393
403394
// console.log('lastPageIdx\t\t', lastPageIdx);
@@ -419,7 +410,7 @@ function headerItemEnabled(page: Page): boolean {
419410
420411
// & Always True //
421412
// Always set current page to editable //
422-
if (currentPageIdx === pageIdx) {
413+
if (currentPageIdx.value === pageIdx) {
423414
return true;
424415
}
425416
@@ -474,17 +465,52 @@ const errorPageIndexes: Ref<number[]> = ref<number[]>([]);
474465
475466
const fieldsHaveErrors = computed(() => errorPageIndexes.value.includes(stepperModel.value - 1));
476467
468+
const $useForm = useForm({
469+
initialValues: modelValue.value,
470+
keepValuesOnUnmount: settings.value?.keepValuesOnUnmount,
471+
validationSchema: validationSchema.value,
472+
valueOnMount: settings.value?.validateOnMount,
473+
});
474+
475+
477476
// ------------------------ Run Validation //
477+
function headerCheckPreviousStepValidation(index: number): void {
478+
const previousIndex = index === 0 ? 0 : index - 1;
479+
const previousPage = computedPages.value[previousIndex];
480+
481+
if (!previousPage || !previousPage.fields) {
482+
return;
483+
}
484+
485+
previousPage.fields.forEach((field) => {
486+
$useForm.validateField(field.name, {}, { name: field.name })
487+
.then((response: ValidateResult) => {
488+
const errors = response.errors as unknown as ValidateResult['errors'];
489+
490+
if (errors.length) {
491+
stepperModel.value = previousIndex + 1;
492+
currentPageHasErrors.value = true;
493+
494+
setPageToError(previousIndex, previousPage, 'submit');
495+
return;
496+
}
497+
498+
removePageError(previousIndex);
499+
});
500+
});
501+
}
502+
478503
function runValidation(
479-
validate: () => Promise<ValidateResult>,
480504
source = 'submit',
481505
next: () => void = () => { },
482506
): void {
483-
validate()
507+
if (!parentForm.value) {
508+
return;
509+
}
510+
511+
$useForm.validate()
484512
.then((response: ValidateResult) => {
485513
const errors = response.errors as unknown as ValidateResult['errors'];
486-
487-
488514
checkForPageErrors(errors, source, next);
489515
})
490516
.catch((error: Error) => {
@@ -507,8 +533,7 @@ function removePageError(pageIndex: number): void {
507533
508534
// ------------------------ Check the if the page has errors //
509535
function checkForPageErrors(errors: ValidateResult['errors'], source: string, next = () => { }): void {
510-
const currentPage = stepperModel.value - 1;
511-
const page = computedPages.value[currentPage];
536+
const page = computedPages.value[currentPageIdx.value];
512537
513538
if (!page) {
514539
return;
@@ -551,15 +576,15 @@ function setPageToError(pageIndex: number, page?: Page, source = 'submit'): void
551576
let debounceTimer: ReturnType<typeof setTimeout>;
552577
553578
function onFieldValidate(field: Field, next: () => void): void {
554-
const errors = parentForm.value?.errors as ValidateResult['errors'];
579+
const errors = $useForm.errorBag as ValidateResult['errors'];
555580
const shouldAutoPage = (field.autoPage || settings.value.autoPage ? next : null) as () => void;
556581
557582
// If autoPage //
558583
if (field?.autoPage || settings.value?.autoPage) {
559584
560585
if (parentForm.value) {
561586
// First validate the page before proceeding to the next page //
562-
(parentForm.value as { validate: () => Promise<ValidateResult>; }).validate()
587+
$useForm.validate()
563588
.then((res: ValidateResult) => {
564589
if (res.valid) {
565590
// debounce next //
@@ -571,8 +596,7 @@ function onFieldValidate(field: Field, next: () => void): void {
571596
return;
572597
}
573598
574-
const currentPage = stepperModel.value - 1;
575-
const page = computedPages.value[currentPage];
599+
const page = computedPages.value[currentPageIdx.value];
576600
const pageIndex = computedPages.value.findIndex((p) => p === page);
577601
578602
setPageToError(pageIndex, page, 'validating');
@@ -585,14 +609,18 @@ function onFieldValidate(field: Field, next: () => void): void {
585609
return;
586610
}
587611
588-
checkForPageErrors(errors, 'field', shouldAutoPage);
612+
$useForm.validateField(field.name, {}, { name: field.name })
613+
.then(() => {
614+
checkForPageErrors($useForm.errorBag.value, 'field', shouldAutoPage);
615+
616+
});
589617
}
590618
591619
592620
// -------------------------------------------------- Submit //
593-
function onSubmit(values: any): void {
621+
const onSubmit = $useForm.handleSubmit((values: any): void => {
594622
emit('submit', values);
595-
}
623+
});
596624
597625
598626
// ------------------------------------------------ Callbacks //

0 commit comments

Comments
 (0)