2
votes

I have created an app with Vue with some different views/components. The views/components have to get run through in an specific order (MainPage -> Step1 -> Step2 -> Step3 -> FinalPage --> MainPage...).

As I am learning Vue I managed the navigation with v-if directives which worked well, as there are no URLs, so I was able to implement the logic in each view/component like

//pseudocode in each view/component
checkConditionsToMoveOn ? moveToNextPae : showErrorWithModalAndStayOnPage;

Getting more professional I implemented vue-router, which somehow broke my app as I am able now to trick the logic to pass a step with just changing the URL manually.

I can think of implementing some navigation guards, but then I have to move the conditions to my router/index.js file, which I think is not really pretty.

Is there any best practice to solve this problem?

I'd appreciate a hint, thanks.

2

2 Answers

2
votes

Using Vuex, track the current state of step progress:

state: {
  step: 1
}

When the user visits any step, you'll check this state. And if the requested step route doesn't match this, you'll redirect to it. To implement this, define the route with route params:

// import store from '@/store';
{
  path: '/step/:sid',
  name: 'step',
  component: Step,
  ...

Now you can direct your app to routes like http://localhost:8080/step/5. The variable to.params.sid would match the 5 in that route. So, in the beforeEnter guard, check that the param matches the store's step.

  ...
  beforeEnter: (to, from, next) => {
    const nextStep = parseInt(to.params.sid);   // convert string param to number
    if (nextStep === store.state.step) {        // is this the current allowed step?
      next();                                   // if so, it's ok
    } else {                                    // if not, direct to that step
      next({ name: 'step', params: { sid: store.state.step }})
    }
  }
},

Whenever a step is completed, you'll increment the store step and then direct the router to the next step.

Extra:

You could also change the if condition to allow for visiting previous steps:

if (nextStep > 0 && nextStep <= store.state.step) {
1
votes

I had similar issue.

I think best solution is put steps into single route. It is easy to handle and hard to trick the logic.

But, if you must split steps into routes for some reasons (each step is so big to put in single route or ...), you can use step counter as global (above router-view).

I suggest two way to set global value.

  1. Set value in App.vue (contains router-view) as data. Serve value as props to every routes. Each route checks step is valid and change url to valid step using $router.replace to prevent backward navigation. If step is valid for this url, process the logic and emit new step number before change url.

App.vue

<template>
  <router-view :step="fooStep" @changeStep=changStep />
</template>

<script>
export default {
  data () {
    return {
      fooStep: 0
    }
  },
  methods: {
    changeStep (value) {
      this.step = value
    }
  }
}

SomeComponent.vue

<script>
export default {
  name: 'Step3', /* optional */
  props: ['step'],
  mounted () {
    /* some logic to check step is valid */
    if (this.step !== 3) {
      this.$router.replace(this.step > 0 ? '/step' + this.step : '/')
    }
  },
  methods: {
    submit () {
      ...
      /* after submit data, send signal to App.vue to change step value */
      this.$emit('changeStep', 4)
      /* change url */
      this.$router.replace('step4')
    }
  }
}
</script>
  1. Second way is using Vuex the vue store. Vuex is good way to manage global value of vue app. My example is too brief that violate vuex rule. Find more information about Vuex. It is too long to write here.

SomeComponent.vue

<script>
export default {
  name: 'Step3', /* optional */
  mounted () {
    /* some logic to check step is valid */
    const step = this.$store.state.step
    if (step !== 3) {
      this.$router.replace(step > 0 ? '/step' + step : '/')
    }
  },
  methods: {
    submit () {
      ...
      /* after submit data, store new step value in vuex */
      /* change the value directly is not good */
      this.$store.state.step = 4 
      /* change url */
      this.$router.replace('step4')
    }
  }
}
</script>