Vue-router + Vuex + Stack navigator



  • I would like to share with you an alternative that I developed to join the vue-router and the Onsen navigator.

    I have not yet integrated 100% with all the features of the vue-router, but it is already possible to navigate between the routes managing the stack navigation.

    Based on Kitchensink (https://github.com/OnsenUI/vue-onsenui-kitchensink/blob/master/src/store.js), I created my navigation store too.

    export default {
      strict: true,
      namespaced: true,
      state: {
        goingBack: false, // Its used when user click on back button
        stack: [],
        options: {
          animation: 'slide'
        }
      },
      mutations: {
        push (state, page) {
          state.stack.push(page)
        },
        pop (state) {
          state.goingBack = false
    
          if (state.stack.length > 1) {
            state.stack.pop()
          }
        },
        replace (state, page) {
          state.stack.pop()
          state.stack.push(page)
        },
        reset (state, page) {
          state.goingBack = false
    
          state.stack = [page || state.stack[0]]
        },
        options (state, newOptions = {}) {
          state.options = newOptions
        },
        toggleGoingBack (state, shouldGoingBack) {
          if (typeof shouldGoingBack === 'boolean') {
            state.goingBack = shouldGoingBack
          } else {
            state.goingBack = !state.goingBack
          }
        }
      }
    }
    

    I used goingBack to router knows when the user is using a route with same level but is going back.

    And my app component is:

    <template lang="pug">
      v-ons-splitter
        v-ons-splitter-side(swipeable collapse="" width="260px"
          :open.sync="splitterIsOpen")
          menu-page
    
        v-ons-splitter-content
          v-ons-navigator(
            :page-stack="pageStack"
            :pop-page="storePop"
            :options="options")
    </template>
    
    <script>
    import MenuPage from './pages/Menu'
    
    export default {
      computed: {
        pageStack () {
          return this.$store.state.navigator.stack
        },
        options () {
          return this.$store.state.navigator.options
        },
        splitterIsOpen: {
          get () {
            return this.$store.state.splitter.open
          },
          set (newValue) {
            this.$store.commit('splitter/toggle', newValue)
          }
        }
      },
      methods: {
        storePop () {
          this.$store.commit('navigator/toggleGoingBack')
    
          if (window.history.length <= 2) {
            this.$router.push({ name: 'home' })
          } else {
            this.$router.go(-1)
          }
        }
      },
      components: {
        MenuPage
      }
    }
    </script>
    

    And after this, I need to tell the router how his work will stack navigation:

    import Vue from 'vue'
    import Router from 'vue-router'
    
    import store from '@/store'
    import HomePage from '@/pages/Home'
    import HostPage from '@/pages/Host'
    import HostItemPage from '@/pages/HostItem'
    
    Vue.use(Router)
    
    const router = new Router({
      routes: [
        { path: '/', name: 'home', component: HomePage },
        {
          path: '/host/:profile?',
          name: 'host',
          component: HostPage,
          children: [
            {
              path: 'items/:item?',
              name: 'host-item',
              component: HostItemPage
            }
          ]
        }
      ]
    })
    
    router.beforeEach((to, from, next) => {
      store.commit('splitter/toggle', false)
    
      // When user access root path, clear stack before ends request
      if (to.path === '/') {
        store.commit('navigator/reset', HomePage)
        return next()
      }
    
      // Every time when stack is empty, push `HomePage` as first
      if (store.state.navigator.stack.length <= 0) {
        store.commit('navigator/push', HomePage)
      }
    
      // Based on https://github.com/vuejs/vue-router/blob/dev/examples/transitions/app.js#L21
      const toDepth = to.path.split('/').length
      const fromDepth = from.path.split('/').length
    
      if (from.name && (toDepth < fromDepth || store.state.navigator.goingBack)) {
        store.commit('navigator/pop')
      } else {
        if (toDepth === fromDepth) {
          const item = to.matched[to.matched.length - 1]
    
          store.commit('navigator/push', item.components['default'])
        } else {
          store.commit('navigator/reset')
    
          to.matched.forEach((item) => {
            store.commit('navigator/push', item.components['default'])
          })
        }
      }
    
      next()
    })
    
    export default router
    


  • Refactoring my code:

    router.beforeEach((to, from, next) => {
      store.commit('splitter/toggle', false)
    
      // When user access root path, clear stack before ends request
      if (to.path === '/') {
        store.commit('navigator/reset', HomePage)
        return next()
      }
    
      const toMatch = to.matched[to.matched.length - 1].components['default']
      const toParentMatch = to.matched[to.matched.length - 2]
        ? to.matched[to.matched.length - 2].components['default']
        : null
      const fromParentMatch = from.matched[from.matched.length - 2]
        ? from.matched[from.matched.length - 2].components['default']
        : null
    
      if (from.name && (toMatch === fromParentMatch || store.state.navigator.goingBack)) {
        store.commit('navigator/pop')
      } else if (toParentMatch === fromParentMatch) {
        store.commit('navigator/push', toMatch)
      } else {
        const stack = to.matched.map((item) => item.components['default'])
        store.commit('navigator/reset', [HomePage].concat(stack))
      }
    
      next()
    })
    


  • Dear raphox,

    I would like to know more about this and maybe a link to a working project on github would be great !


Log in to reply