skip to Main Content

i’m writing a personal project for my portfolio.
I’m making a registration site.

So the user will fill: personal data, shipping info and then will see a recap.

I have a view (Register.vue) where I put a stepper from Vuetify to make things faster.

There are three steps: PersonalDataForm (a separated component), ShippingForm (another separated component) and recap.

So as for every step the user can fill the form and then proceed to the next step.

The problems I am facing are:

  1. I want to make the "next" button disabled unless the form is completed and i don’t know how to deal with the vuetify stepper
  2. I don’t know how to pass props so i can’t make the first problem solvable.

REGISTER VIEW

<template>
  <v-stepper color="deep-purple-darken-1" :items="['Dati Anagrafici', 'Indirizzo di spedizione', 'Riepilogo']" class="my-5">
    <template v-slot:item.1>
      <v-card>
        <PersonalDataForm @formDataSubmitted="handlePersonalDataForm" :isFormIncomplete="personalDataFormIsIncomplete"/>
      </v-card>
    </template>

    <template v-slot:item.2>
      <v-card>
        <ShippingForm @formDataSubmitted="handleShippingForm" :isFormIncomplete="shippingFormIsIncomplete"/>
      </v-card>
    </template>

    <template v-slot:item.3>
      <v-card>
        <Recap @completed="submitData" :disabled="isFormIncomplete"/>
      </v-card>
    </template>
  </v-stepper>
</template>

<script setup>
import { ref, computed } from 'vue';
import PersonalDataForm from '@/components/forms/PersonalDataForm.vue';
import ShippingForm from '@/components/forms/ShippingForm.vue';
import Recap from '../components/Recap.vue';

const personalDataFormIsIncomplete = ref(true);
const shippingFormIsIncomplete = ref(true);

const isFormIncomplete = computed(() => {
  return personalDataFormIsIncomplete.value || shippingFormIsIncomplete.value;
});

const handlePersonalDataForm = (formData) => {
  console.log("Received personal data:", formData);
  personalDataFormIsIncomplete.value = false;
};

const handleShippingForm = (formData) => {
  console.log("Received shipping data:", formData);
  shippingFormIsIncomplete.value = false;
};

const submitData = () => {
  console.log('Submitting data...');
};
</script>
```

PersonalDataForm.vue
```
<template>
  <v-container>
    <v-card class="rounded-lg py-4 px-8" elevation="8">
      <form @submit.prevent="handleSubmit">
        <v-row>
          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.firstName"
              :error-messages="v$.firstName.$errors.map((e) => e.$message)"
              label="Nome"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.firstName.$touch"
              @input="v$.firstName.$touch"
            ></v-text-field>
          </v-col>

          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.lastName"
              :error-messages="v$.lastName.$errors.map((e) => e.$message)"
              label="Cognome"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.lastName.$touch"
              @input="v$.lastName.$touch"
            ></v-text-field>
          </v-col>

          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.birthDate"
              :error-messages="v$.birthDate.$errors.map((e) => e.$message)"
              type="date"
              label="Data di Nascita"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.birthDate.$touch"
              @input="v$.birthDate.$touch"
            ></v-text-field>
          </v-col>

          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.birthPlace"
              :error-messages="v$.birthPlace.$errors.map((e) => e.$message)"
              label="Città di Nascita"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.birthPlace.$touch"
              @input="v$.birthPlace.$touch"
            ></v-text-field>
          </v-col>

          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.fiscalCode"
              :error-messages="v$.fiscalCode.$errors.map((e) => e.$message)"
              label="Codice Fiscale"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.fiscalCode.$touch"
              @input="v$.fiscalCode.$touch"
            ></v-text-field>
          </v-col>
        </v-row>
        <v-btn class="me-4" @click="handleSubmit"> submit </v-btn>

      </form>
    </v-card>
  </v-container>
</template>

<script setup>
import { ref, computed } from 'vue';
import { useVuelidate } from "@vuelidate/core";
import { required } from "@vuelidate/validators";

const state = ref({
  firstName: "",
  lastName: "",
  birthDate: "",
  birthPlace: "",
  fiscalCode: "",
});

const rules = {
  firstName: { required },
  lastName: { required },
  birthDate: { required },
  birthPlace: { required },
  fiscalCode: { required }
};

const v$ = useVuelidate(rules, state.value);

const isFormIncomplete = computed(() => {
  return Object.values(state.value).some(value => !value);
});

function handleSubmit() {
  v$.value.$touch();

  if (!v$.value.$invalid) {
    // Form is valid, proceed with submitting data
    const formData = { ...state.value };
    console.log("Submitting form data:", formData);

    // Here you can call your submit function or perform any necessary action
    // Example: submitFormData(formData);
  } else {
    // Form is invalid, do something (e.g., show error message)
    console.log("Form has validation errors, cannot submit.");
  }
}
</script>
```

ShippingForm.vue
<template>
  <v-container>
    <v-card class="rounded-lg py-4 px-8" elevation="8">
      <form @submit.prevent="handleSubmit">
        <v-row>
          <!-- fare che tipo sia precompilato e non modificabile -->
          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.firstName"
              :error-messages="v$.firstName.$errors.map((e) => e.$message)"
              label="Nome"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.firstName.$touch"
              @input="v$.firstName.$touch"
            ></v-text-field>
          </v-col>

          <!-- fare che tipo sia precompilato e non modificabile -->
          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.lastName"
              :error-messages="v$.lastName.$errors.map((e) => e.$message)"
              label="Cognome"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.lastName.$touch"
              @input="v$.lastName.$touch"
            ></v-text-field>
          </v-col>

          <v-col cols="12" md="6">
            <v-select
              v-model="state.region"
              :items="regions"
              :error-messages="v$.region.$errors.map((e) => e.$message)"
              label="Regione"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.region.$touch"
              @input="v$.region.$touch"
            ></v-select>
          </v-col>

          <v-col cols="12" md="6">
            <v-select
              v-model="state.province"
              :items="provinces"
              :error-messages="v$.province.$errors.map((e) => e.$message)"
              label="Provincia"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.province.$touch"
              @input="v$.province.$touch"
            ></v-select>
          </v-col>

          <v-col cols="12" md="6">
            <v-select
              v-model="state.city"
              :items="cities"
              :error-messages="v$.city.$errors.map((e) => e.$message)"
              label="Città"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.city.$touch"
              @input="v$.city.$touch"
            ></v-select>
          </v-col>

          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.houseNumber"
              :error-messages="v$.houseNumber.$errors.map((e) => e.$message)"
              label="Civico"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.houseNumber.$touch"
              @input="v$.houseNumber.$touch"
            ></v-text-field>
          </v-col>

          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.email"
              :error-messages="v$.email.$errors.map((e) => e.$message)"
              label="E-mail"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.email.$touch"
              @input="v$.email.$touch"
            ></v-text-field>
          </v-col>

          <v-col cols="12" md="6">
            <v-text-field
              v-model="state.phoneNumber"
              :error-messages="v$.phoneNumber.$errors.map((e) => e.$message)"
              label="Telefono"
              required
              variant="underlined"
              color="deep-purple-lighten-1"
              @blur="v$.phoneNumber.$touch"
              @input="v$.phoneNumber.$touch"
            ></v-text-field>
          </v-col>
        </v-row>
        <v-btn class="me-4" type="submit" @click="handleSubmit"> submit </v-btn>
      </form>
    </v-card>
  </v-container>
</template>

<script setup>
import { reactive, computed, defineProps } from 'vue';
import { useVuelidate } from "@vuelidate/core";
import {
  email,
  required,
  numeric,
  minLength,
  maxLength,
} from "@vuelidate/validators";

const props = defineProps(['formData']);
const emit = props['onUpdate:formData'];

const initialState = {
  firstName: "",
  lastName: "",
  region: "",
  province: "",
  city: "",
  houseNumber: "",
  email: "",
  phoneNumber: "",
};

const state = reactive({
  ...initialState,
});

const isFormIncomplete = computed(() => {
  return Object.values(state).some(value => !value); // Check if any field is empty
});


const regions = ["Emilia", "Lombardia", "Calabria", "Piemonte"];

const provinces = ["piacenza", "Milano", "Cosenza", "Torino"];

const cities = ["Caorso", "Assago", "Lamezia", "Settimo Torinese"];

const rules = {
  firstName: { required },
  lastName: { required },
  region: { required },
  province: { required },
  city: { required },
  houseNumber: { required, numeric },
  email: { required, email },
  phoneNumber: {
    required,
    numeric,
    minLength: minLength(12),
    maxLength: maxLength(12),
  },
};

const v$ = useVuelidate(rules, state);

const getData = computed(() => {
  return {
    firstName: state.firstName,
    lastName: state.lastName,
    region: state.region,
    province: state.province,
    city: state.city,
    houseNumber: state.houseNumber,
    email: state.email,
    phoneNumber: state.phoneNumber,
  };
});


function handleSubmit() {
  v$.value.$touch();
  emit('formDataSubmitted', formData.value);

  if (!v$.value.$invalid) {
    // Form is valid, proceed with submitting data
    const formData = { ...state };
    console.log("Submitting form data:", formData);

    // Here you can call your submit function or perform any necessary action
    // Example: submitFormData(formData);
  } else {
    // Form is invalid, do something (e.g., show error message)
    console.log("Form has validation errors, cannot submit.");
  }
}
</script>
`

2

Answers


  1. Simple Solution would be have boolean flags for each form type. If a form submission is valid update the correponsding boolean flag and show/disable the next button.

    You need to create the formDataSubmitted event and emit this event on successful form submission. Listen for this event in the parent component and update the boolean flag.

    <script setup>
    const emit = defineEmits(['formDataSubmitted']);
    
    function handleSubmit() {
      ...other code
    
      if(formValid) {
         emit('formDataSubmitted', formData);
      }
    }
    </script>
    
    

    Learn more about how events work in Vue here: Vue Event Emitter

    Login or Signup to reply.
  2. If you have a simple and direct parent-child relationship/structure, emit is your friend. If you have nested structures, emit is awkward. Because you have to work your way up through any hierarchy. This is where pinia can help. Pinia is a storage. Here you define actions and can access them globally. This simplifies life enormously and makes you independent of parent-child structures.

    https://pinia.vuejs.org

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search