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 !


  • Onsen UI

    @raphox Hey, finally got some time to test vue-router + navigator. I think v-ons-navigator is quite robust and is easy to plug vue-router. What do you think about this?

    router.beforeEach((to, from, next) => {
      store.commit('navigator/reset', to.matched.map(m => m.components.default));
      next();
    });
    

    It passes the components in the new route to the navigator and it figures about the rest. The map function might need to be changed depending on the app. This example specifically assumes every route is a page (no small child views) and all of them use the default view.

    Alternatively, it can also be used without Vuex in the same place where you declare your pageStack array:

    watch: {
      '$route' (to, from) {
        this.pageStack = to.matched.map(m => m.components.default);
      }
    }
    

    The back buttons can simply run this.$router.go(-1) if the routes are linear (always parent -> child) or this.$router.push({name: this.$route.matched[this.$route.matched.length - 2].name}) if the routes can switch between sibling routes (routeA/child1 -> push -> routeA/child2 -> pop -> routeA/).

    The performed animations depend on the pageStack length and the current top page. If the top page changed and the new route length is lower than the previous one, it will show a pop animation. Anything else is considered a push (or replace, with push animation). Therefore, if moving among child views (routeA/child1/ -> routeA/child2/), one should replace the routes. Otherwise, it will end up doing another push animation when going back with go(-1) since the length doesn’t change. I guess v-ons-tabbar is a better fit for that kind of route. v-ons-navigator should only take care of push-pop routes (routeA/child1 -> route1/child1/child1-1/).


  • Onsen UI

    vue-onsenui + vue-router example here without Vuex: https://frandiox.github.io/onsenui-vue-router

    Repo: https://github.com/frandiox/onsenui-vue-router



  • @Fran-Diox Hi.

    I think your code is simple but really works.

    In my second code Im giving attention to:

    • direct access by url without referer. Like you.
    • store home page on the stack of pages
    • links with history.back()
    • controll push or pop to animation based on level route access

    @Eric-Hughes I will create. Thanks for your interest.


Log in to reply