0
votes

Here is my setup:

Vue 2.5.16 Veux Vue router

I have setup a simple router view that looks for a child component inside a parent one and the url structure is;

/folders/parent-uuid/child-uuid

I have a component for the parent below:

<template lang="html">
  <div>
    <!-- Articles -->
    <div class="flex-none w-100 mv3 gray"><p>Bookmarks({{ subFolders.contentDetails.articles.length }})</p></div>
    <div class="flex flex-row flex-wrap justify-start items-start">
      <div v-for="article in subFolders.contentDetails.articles" class="pointer article-item relative mb4">
        <a v-on:click.stop.prevent="checkFolder(article.uuid)" :class="[{highlight:selectedItems.includes(article.uuid)}, 'absolute w-100 h-100 left-0 top-0 highlight-area z-3']" href="#"><div class="absolute top-2" style="width: 18px;height: 18px;right: 3.05rem;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path v-if="selectedItems.includes(article.uuid)" fill="#ea792e" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/><path v-else fill="#ffffff" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/></svg></div></a>
        <a :class="[{active:selectedItems.includes(article.uuid)}, 'link']" v-bind:href="article.contentUrl">
          <div :class="[{active:selectedItems.includes(article.uuid)}, 'contentImage br3 overflow-hidden']">
            <img class="w-100" :src=article.titleImage data-flickity-lazyload="article.titleImage">
          </div>
          <div v-if="article.contentType == 'Behaviour'" class="black">
            <h4 class="f3">{{article.contentTitle}}</h4>
          </div>
          <div v-else class="black">
              <h4 class="f3">{{article.contentTitle}}</h4>
          </div>
          <div v-if="article.contentType == 'Behaviour'">
          </div>
          <div v-else class="content no-margin">
            <p class="f3">{{article.savedDate | moment("D MMM YYYY")}}</p>
          </div>
        </a>
      </div>
    </div>
    <!-- end v-if -->
    <div class="flex-none w-100 mt3 gray folders"><div class="ph5"><p>Subfolders({{ subFolders.subFolders.length }})</p></div></div>
    <div class="flex flex-row flex-wrap justify-start items-start">
      <div class="folder-item folder z-0 pointer">
        <div class="relative db h-100 bg-light-gray folder-new br3 mb4">       
          <div v-on:click="addFolder($event)" class="top aspect-ratio--object">
            <div class="dt w-100 h-100 no-margin showText pv6">
              <div class="dtc v-mid tc ">
                <svg class="dib v-mid icon-sprite" viewBox="0 0 38.89 38.89" width="40" height="40">
                 <circle cx="19.45" cy="19.45" r="19.45" fill="#ea792e"/><path stroke="#ffffff" stroke-width="3" d="M19.45 12.26v14.37M12.26 19.45h14.37" />
                </svg>
                <p>Create new folder</p>
              </div>
            </div>
          </div>
          <div class="bottom aspect-ratio--object">
            <div class="dt w-100 h-100 ">
              <div class="dtc v-mid tc no-margin showText pv6">
                <p>Explore the library and add to this folder</p>
                <form id="submit-folder" @submit.prevent="sendForm" class="mv3">
                  <input type="text" name="folderName" value="" placeholder="Add folder name in here..." v-model="folderName">
                  <input class="db center input-reset bg-orange b--none br2 white mv3 f3 pv3 ph4" style="font-family:'Open Sans',Helvetica Neue,sans-serif;" type="submit" name="Submit">
                </form>
              </div>
            </div>
          </div>
          <div style="visibility: hidden;" class="image-blocks br3 overflow-hidden relative flex flex-wrap">
            <div><img src="http://***/static-assets/images/EMPTY-FOLDER-GREY-1.jpg" alt=""></div>
            <div><img src="http://***/static-assets/images/EMPTY-FOLDER-GREY-2.jpg" alt=""></div>
            <div><img src="http://***/static-assets/images/EMPTY-FOLDER-GREY-3.jpg" alt=""></div>
          </div>
        </div>
      </div>
      <div v-for="folder in subFolders['subFolders']" class="pointer folder folder-item relative">
        <a v-on:click.stop.prevent="checkFolder(folder.uuid)" :class="[{highlight:selectedItems.includes(folder.uuid)}, 'absolute w-100 h-100 left-0 top-0 highlight-area z-3']" href="#"><div class="absolute top-2" style="width: 18px;height: 18px;right: 3.05rem;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path v-if="selectedItems.includes(folder.uuid)" fill="#ea792e" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/><path v-else fill="#ffffff" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/></svg></div></a>
        <router-link :class="[{active:selectedItems.includes(folder.uuid)}, 'z-0 db relative link']" :to="`/folders/${folderUUID}/${folder.uuid}`">
          <div class="image-blocks br3 overflow-hidden relative">

              <div class="" v-for="image in folder.topThreeThumbnails">
                <img class="" :src=image>
              </div>

          </div>
        </router-link>
        <div :class="[{active:selectedItems.includes(folder.uuid)}, 'content']">
          <router-link :to="`/folders/${folder.uuid}`" :class="[{active:selectedItems.includes(folder.uuid)}, 'link black']">
            <h3>{{folder.folderName}}</h3>
            <p>{{folder.subFolderCount}} subfolders, {{folder.totalElements}} elements</p>
          </router-link>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
  name: 'subfolderListParent',
  data() {
    return {
      selected: [],
      selectedArticles: [],
      folder: '',
      folderName: '',
      folderUUID: this.$route.params.uuid
    }
  },
  mounted: function () {
    this.$store.dispatch('LOAD_SUBFOLDERS_LIST',this.$route.params.uuid)
    this.$store.dispatch('LOAD_FOLDERS_LIST')
    this.$store.state.selectedItems = [];
    this.$route.meta.title = this.$store.getters.openSubfolderTitle;
  },
  props: ['uuid','subfolderName'],
  computed: {
   ...mapState([
      'subFolders',
      'allFolders',
      'allArticles',
      'selectedItems',
      'selectedFolder'
    ]),
   ...mapGetters([
      'openSubfolderTitle',
    ])
  },
  methods: {
    checkFolder: function(item){
      this.$store.dispatch('SELECT_FOLDER',item)
    },
    addFolder: function(event){
      const target = event.currentTarget.parentNode,
            top    = target.children[0],
            bottom = target.children[1];
      if(bottom.classList.contains('active')){
        top.classList.remove('hidden');
        bottom.classList.remove('active');
      } else {
        top.classList.add('hidden');
        bottom.classList.add('active');
      }      
    },
    sendForm: function(event){
      this.$store.dispatch('CREATE_FOLDER',{'folder':this.folderName,'uuid':this.$route.params.uuid})
      this.folderName = '';
    },
    setTitle: function(){
      this.$route.meta.title = this.$store.getters.openSubfolderTitle;
    }
  },
  watch: {
    '$route': function(from, to) {
      this.$route.meta.title = this.$store.getters.openSubfolderTitle;
    }
  }
}
</script>

This is a pretty straight forward template that looks for some data in vuex etc and puts it into the template. Where I am coming into issues is when I try to get the child component loaded from a nested view.

The router code is as follows:

{
    path: '/folders/:uuid', 
    component: Folder,
    name: 'subfolderListParent', 
    props: true,
    meta: {
      bcDynamic: true,
      bcGetter: 'openSubfolderTitle', // <breadcrumb> will use this getter to get the user from vuex
      bcLinkText: state => state, // once we have the user, we use this function to format the LinkText dnynamically,
      bcLoadingText: 'Loading title...' // This will be shown while Data is loading
    },
    children: [
      {
        path: '/:parentUUID',
        props: true,
        components: {
          child: FolderChild
        },
        meta: {
          bcDynamic: true,
          bcGetter: 'openSubfolderTitle', // <breadcrumb> will use this getter to get the user from vuex
          bcLinkText: state => state, // once we have the user, we use this function to format the LinkText dnynamically,
          bcLoadingText: 'Loading title...' // This will be shown while Data is loading
        },
      }
    ]
  },

And the app code where it all gets rendered is as follows:

<template>
<div id="app" style="clear:both;min-height:100vh;" class="flex-l flex-row flex-wrap items-stretch">
  <transition name="slide-fade">
    <div class="fixed right-2 br2 ph4 pv3 bg-orange white z-3" v-on:click="$store.state.noteText.note = !$store.state.noteText.note" v-bind:key="$store.state.noteText.note" v-if="$store.state.noteText.note === true">
      <p class="white ma0 pa0 dib v-mid">{{$store.state.noteText.note}}</p>
    </div>
  </transition>
  <MainNav/>
    <div v-if="$route.path === '/from-canvas8'"></div>
    <div class="flex-none" v-else>
      <FolderActions/>
    </div>
  <div class="dib w-75-l w-100 v-top ph5 appWrapper">
    <router-view></router-view>   
  </div>
</div>
</template>

<script>
import MainNav from './components/MainNav.vue'
import FolderList from './components/FolderList.vue'
import FolderActions from './components/FolderActions.vue'
import ScrapbookHome from './components/ScrapbookHome.vue'
import { mapState } from 'vuex'
export default {
  components: {
    MainNav,
    FolderList,
    FolderActions,
    ScrapbookHome
  },
  name: 'scrapbook',
  data () {
    return {
      welcomeMessage: 'Story Navigation'
    }
  },
  methods: {

    updateScroll: function(){
      const nav = document.querySelector('.scrapbook-actions');
      const navTop = nav.offsetTop;

      function stickyNavigation() {

        if (window.scrollY >= navTop) {
          // nav offsetHeight = height of nav
          nav.classList.add('fixed');
        } else {
          nav.classList.remove('fixed');
        }
      }

      window.addEventListener('scroll', stickyNavigation); 
    },

  },
  mounted: function () {
    // this.$store.dispatch('LOAD_FOLDERS_LIST')
    //this.setNavHeight()
    this.$store.dispatch('GET_USER')
  },
  watch: {
    '$route': function(from, to) {
      //this.updateScroll()
      //this.setNavHeight()
    }
  }
} 
</script>

For some reason the <router-view /> will not show this child component.

Can anyone help at all point me as to why this won't work?

1
I am pretty sure it is trying to mount FolderChild in the Folder component. I am pretty sure this even fails if you move the current parent component to its own child route with route '' (aka the empty string). I am going to try to see if I can reproduce how I solved it for a previous project.Sumurai8
Shouldn't path: '/:parentUUID' be path: ':parentUUID'Bennett Dams
@Sumurai8did you manage to have a look?M dunbavan

1 Answers

1
votes

When you nest routes, each route part will be mounted in the component of the parent route. In your case Folder will be mounted in App and FolderChild will be mounted in Folder. As far as I know, this is always the case with nested routes, even if you do not define a component for a particular sub-route. As far as I know there is not something like a fallthrough option that mounts a component in an applicable ancestor rather than the direct parent.

There are two ways to solve this problem. The easiest way is to not use child routes unless you have a shared boilerplate around each of the subroutes. You would get something like the following. You can somewhat organise your routes by using comments.

export default [
  {
    // some other routes 
  },

  // Folder routes
  {
    path: '/folders/:uuid/:parentuuid',
    component: FolderChild,
    name: 'folderchild'
  },
  {
    path: '/folders/:uuid',
    component: Folder,
    name: 'folders'
  }
];

The other way is by still using the nested routes, but by putting in a dummy component with a single router view in parent routes where you do not have anything else to render.

// DummyView.vue
<template>
  <router-view />
</template>

<script>
// Used to fill parent routes that need to have children mounted in them
export default {
  name: 'dummy-view'
}
</script>

You then move your "default" route to a child route with an empty path and use this dummy view to render the parent route, and mount both Folder and FolderChild in this dummy view.

export default [
  {
    path: "/folders/:uuid",
    component: DummyView,
    children: [
      {
        path: "",
        component: Folder
      },
      {
        path: ":parentUUID",
        component: FolderChild
      }
    ]
  }
];

This solution will become particularly aweful when you have multiple (named) views in a component, which all need to be filled for child views. The nice part is that if you have shared logic, you can use these steps to gradually build the view you want without needing to resort to these dummy components that only serve the purpose of having a place to mount child components.

Edit Vue Template