Bonjour,

Voila je rencontre un petit problème avec mon code. Je souhaite mettre en place un formulaire par étape avec une validation de chaque étape. Mais je rencotre un problème lors de l'emission de mon event avec $emit rien ne se passe, et $dispatch, vuejs me dit que ce n'est pas une fonction.

Dans mon fichier Presentation.vue, j'essaie d'emettre avec un this.$emit('complete') à son parent mais je ne passe pas dedans. Je ne comprends pas pourquoi.

Fichier App.vue

<template>
  <div id="app">
    <steps>
      <step>
        <presentation></presentation>
      </step>
      <step>2ème étape</step>
      <step>3ème étape</step>
      <step>4ème étape</step>
    </steps>
  </div>
</template>

<script>
  import Steps from './components/Steps'
  import Step from './components/Step'
  import Presentation from './components/Presentation'

  export default {
    components: {
      Steps, Step, Presentation
    }
  }
</script>

Fichier Steps.vue

<template>
  <div class="list-step" @next="nextStep">
    <slot></slot>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        current: 0,
        steps: []
      }
    },
    computed: {
      stepsCount () {
        return this.steps.length
      }
    },
    methods: {
      nextStep () {
        if (this.current < this.stepsCount - 1) {
          this.current++
        }
      },
      previousStep () {
        if (this.current > 0) {
          this.current--
        }
      }
    },
    mounted () {
      this.steps = this.$children
      this.steps.forEach((step, i) => {
        step.index = i
      })
    }
  }
</script>

Fichier Step.vue

<template>
  <div class="step" v-show="visible" @complete="completeStep">
    <slot></slot>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        index: 0,
        complete: false
      }
    },
    computed: {
      visible () {
        return this.index === this.$parent.current
      }
    },
    methods: {
      completeStep () {
        this.complete = true
        this.$emit('next')
      }
    }
  }
</script>

Fichier Presentation.vue

<template>
  <div id="presentation">
    Ici il y aura mon formulaire pour l'étape 1
    <button @click="validation">Valider mon étape</button>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        validate: false
      }
    },
    methods: {
      validation () {
        this.validate = true
        this.$emit('complete')
      }
    }
  }
</script>

Merci d'avance pour votre retour.

Jérémy

6 réponses


PhiSyX
Réponse acceptée

Je pense que tu peux utiliser la balise component pour un chargement de composant dynamique (<component :is="currentStep">) directement dans le fichier App.vue.
Et tu utilises le même principe d'événements @next, @prev, @complete sur ce même composant.

Par ex:
Lors d'une validation d'une étape: this.$emit('next', 'nouveau-composant-a-charger')
Lors d'un retour en arrière: this.$emit('prev')
Lorsque toutes les étapes ont été validées et terminées: this.$emit('complete')

N'hésite pas aussi à utiliser la balise <keep-alive> autour du composant <component> pour que les données, de tes champs de textes, entres composants ne "s'envolent" pas.

Yop.

C'est normal car @complete="completeStep" n'est pas à sa place.
Càd qu'il doit être au même niveau de la balise <presentation>. (qui se situe donc dans App.vue).

(Les envois d'événements vers les parents $dispatch/enfants $broadcast ont été retirés sur cette version; pour ça qu'entre autres, vuejs te dit que $dispatch n'est pas une fonction.)

Merci pour l'info, j'ai mal dû lire la doc ou mal la comprendre. Mais quelle serait la solution dans ce cas avec la version 2 de VueJS ?
Est-ce un problème d'architecture dans mon montage de composant pour faire ce que j'ai envie ?

Aujourd'hui le composant Steps à comme enfant des composant Step, à l'intérieur du composant Step, il y a un composant particulier qui sera à chaque fois un formulaire différent avec des traitements particulier et surtout une méthode validation qui doit permettre au composant Step de passer à complete et donc au composant Steps d'avancer de 1.

Merci d'avance pour votre retour

Merci pour la piste :) le composant dynamique à l'air intéressant !

Par contre, je n'arrive pas à voir l'architecture de mon App.vue au niveau template. Est-ce que je dois garder mon composants Steps avec à l'intérieur mes différents composant Step. Ou je mets directement mes différents Step dans App.vue sans les entourer du composant Steps.

De même, je n'arrive pas à voir où je dois implémenter le html du bouton suivant et précédent ?

Merci.

Tes composants Steps/Step ne servent plus à rien pour le coup, donc oui les mettres directement dans ton fichier App.vue, même si tu peux très bien garder ta structure actuelle.

Ex:

<template>
    <div id="app">
        ...
        <div class="list-step">
            <div class="step">
                <component :is="currentStep"
                           @next="nextplz"
                           ...></component>
            </div>
        </div>
        ...
    </div>
</template>

<script>
  import Presentation from './path/to/Presentation.vue'
  import NomNouveauComposant from './path/to/NomNouveauComposant.vue'

  export default {
    components: {
      Presentation,
      NomNouveauComposant
      // ...
    },

    data () {
      return {
        oldStep: null,
        currentStep: 'presentation'
      }
    }

    methods: {
      nextplz () {
        // ...
      }
    }
  }
</script>

Ensuite pour les boutons Suivant/Retour, tu peux les mettres directement dans le composant en question.

Par exemple pour le composant Presentation

<template>
  <div>
    Présentation.
    ...
    ...
    <button @click="$emit('next', 'nom-nouveau-composant')">Continuer</button>
    <button @click="$emit('prev')">Retour</button>
  </div>
</template>

Merci pour ton aide ;)