I am currently learning Vue JS and practising it by making some apps. I've encountered a problem, which I wasn't able to figure out and I need your help with it.
I have a dashboard component where a user can do CRUD operations for articles and announcements. The template is structured like this:
<template>
<div class="dashboard">
<div class="panel">
<div class="panel-header">
<div>
<router-link to="/settings"
tag="button"
class="btn btn-action"><i class="icon website-icon"></i></router-link>
<router-link to="/settings"
tag="button"
class="btn btn-action"><i class="icon messages-icon"></i></router-link>
</div>
<div class="panel-title">Admin Panel</div>
<div>
<router-link to="/settings"
tag="button"
class="btn btn-action"><i class="icon settings-icon"></i></router-link>
<router-link to="/logout"
tag="button"
class="btn btn-action" style="font-size: 1rem"><i class="icon logout-icon"></i></router-link>
</div>
</div>
<div class="panel-nav">
<ul class="tab tab-block">
<router-link to="/dashboard" exact
tag="li"
class="tab-item"
active-class="active">Dashboard</router-link>
<router-link to="/dashboard/articles"
tag="li"
class="tab-item"
active-class="active">Articles</router-link>
<router-link to="/dashboard/announcements"
tag="li"
class="tab-item"
active-class="active">Announcements</router-link>
</ul>
</div>
<div v-if="$route.path=='/dashboard/articles' || $route.path=='/dashboard/announcements'" class="panel-sub-nav">
<button class="btn btn-unfavorite" style="width: 25%"><i class="icon unfavorite-icon"></i> Unfavorite All</button>
<div style="width: 50%; text-align: center">0 / 5</div>
<button v-if="$route.path=='/dashboard/articles'" class="btn btn-add" style="width: 25%"><i class="icon add-icon"></i> Add Article</button>
<button v-if="$route.path=='/dashboard/announcements'" class="btn btn-add" style="width: 25%"><i class="icon add-icon"></i> Add Announcement</button>
</div>
<div class="panel-body">
<router-view></router-view>
</div>
</div>
</div>
</template>
The route file is structured like this:
import Dashboard from '../components/Dashboard'
import ArticleIndex from '../components/ArticleIndex'
import AnnIndex from '../components/AnnIndex'
import Settings from '../components/Settings'
export const routes = [
{ path: '/dashboard', component: Dashboard, children: [
{ path: 'articles', component: ArticleIndex },
{ path: 'announcements', component: AnnIndex },
] },
{ path: '/settings', component: Settings }
]
The problem is that I want users to access settings component inside the dashboard component. But they should access the settings component through /settings not /dashboard/settings.
Since I've not nested the settings component inside dashboard route.
Settings component is not opening up inside the dashboard component.
What should I do to have Settings component open inside Dashboard component but be accessed through /settings?
I think is a very easy problem to solve and sure some people already asked this question before but unfortunately, I wasn't able to find an answer.
Any help appreciated. Thank you.
Well, what you could do is keep views separated from your components. So what I would do is the following. Create
components/Settings.vue
And
views/DashboardView.vue
views/SettingsView.vue
And then you import your Settings in DashboardView and SettingsView
And in your router you of course import your {x}View components.
Related
well i'm new to vue js and developing an application with a search function.
This is my component where the search results will be rendered.
<script>
import RecipeItem from "../recipe/RecipeItem";
import { baseApiUrl } from "#/global";
import axios from "axios";
import PageTitle from "../template/PageTitle";
export default {
name: "Search",
components: { PageTitle, RecipeItem },
data() {
return {
recipes: [],
recipe: {},
search: '',
}
},
methods: {
getRecipes() {
const url = `${baseApiUrl}/search?search=${this.search}`;
axios(url).then((res) => {
this.recipes = res.data;
});
}
},
watch: {
search() {
const route = {
name: 'searchRecipes'
}
if(this.search !== '') {
route.query = {
search: this.search
}
}
},
'$route.query.search': {
immediate: true,
handler(value) {
this.search = value
}
}
},
};
</script>
<template>
<div class="recipes-by-category">
<form class="search">
<router-link :to="{ path: '/search', query: { search: search }}">
<input v-model="search" #keyup.enter="getRecipes()" placeholder="Search recipe" />
<button type="submit">
<font-icon class="icon" :icon="['fas', 'search']"></font-icon>
</button>
</router-link>
</form>
<div class="result-search">
<ul>
<li v-for="(recipe, i) in recipes" :key="i">
<RecipeItem :recipe="recipe" />
</li>
</ul>
</div>
</div>
</template>
ok so far it does what it should do, searches and prints the result on the screen.
But as you can see I created the search field inside it and I want to take it out of the search result component and insert it in my header component which makes more sense for it to be there.
but I'm not able to render the result in my search result component with my search field in the header component.
but I'm not able to render the result in my search result component with my search field in the header component.
Header component
<template>
<header class="header">
<form class="search">
<input v-model="search" #keyup.enter="getRecipes()" placeholder="Search recipe" />
<router-link :to="{ path: '/search', query: { search: this.search }}">
<button type="submit">
<font-icon class="icon" :icon="['fas', 'search']"></font-icon>
</button>
</router-link>
</form>
<div class="menu">
<ul class="menu-links">
<div class="item-home">
<li><router-link to="/">Home</router-link></li>
</div>
<div class="item-recipe">
<li>
<router-link to="/"
>Recipes
<font-icon class="icon" :icon="['fa', 'chevron-down']"></font-icon>
</router-link>
<Dropdown class="mega-menu" title="Recipes" />
</li>
</div>
<div class="item-login">
<li>
<router-link to="/auth" v-if="hideUserDropdown">Login</router-link>
<Userdropdown class="user" v-if="!hideUserDropdown" />
</li>
</div>
</ul>
</div>
</header>
</template>
Result component
<template>
<div class="recipes-by-category">
<div class="result-search">
<ul>
<li v-for="(recipe, i) in recipes" :key="i">
<RecipeItem :recipe="recipe" />
</li>
</ul>
</div>
</div>
</template>
Keep the state variables in whatever parent component is common to both the header component and the results component. For example, if you have a Layout component something like this:
<!-- this is the layout component -->
<template>
<HeaderWithSearch v-on:newResults="someFuncToUpdateState" />
<ResultsComponent v-bind:results="resultsState" />
</template>
<!-- state and function to update state are in a script here... -->
When the search bar returns results you need to pass that data "up" to the parent component with an $emit call, then the parent component can then pass that state back down to the results component using normal props.
Check out this documentation: https://v2.vuejs.org/v2/guide/components-custom-events.html
Be sure to pay special attention to the .sync part of the documentation and determine if that's something you need to implement as well.
Unless you want to use a more complicated state management library like vuex (which shouldn't be necessary in this case) you can just keep state in a common parent and use $emit to pass up and props to pass down.
I have a Vue routes setup with the following parent and child routes:
{
path: '/dashboard', component: dashboard,
children: [
{
path:'report', component: report
}
]
},
And in the parent component I have some card menus to navigate to the child component and also a router-view:
<template>
<div class="container mt-5">
<div class="row mt-0 h-100">
<!-- menu item-->
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-12 my-auto" id="report-card">
<router-link to="/dashboard/report" class="card2">
<div class="d-flex flex-column justify-content-center">
<div class="card-icon p-3">
<img :src="icons[0]"/>
</div>
<h3>Make a Report</h3>
<p class="small">Make a report of a crime or suspicious activities</p>
</div>
</router-link>
</div>
</div>
<router-view></router-view>
</div>
</template>
When I click on the router link, it does render the child component, but in the parent view.
Is there a way in which I could replace the parent view entirely with the child route instead of rendering or appending to the parent?
I am not sure if this is the right way to do it but you can use v-if with this.$route.name to check router name and render what you want to render.
For example your router will be like:
{
path: '/dashboard',
name: "Dashboard",
component: dashboard,
children: [
{
path:'report',
name: "Report",
component: report
}
]
},
and you can simply check if you are on parent route or not like this:
<template>
<div>
<div v-if="this.$route.name === 'Dashboard' ">
<!-- your homepage component -->
</div>
<router-view></router-view>
</div>
</template>
I am putting together a SPA using Vue with VueRouter. The site works and I am able to click the link and get to the menu items. I see in the url that the # still preceedes the link name. For example, if I click on Services in the menu, the url states https://sitename.com/#services
I am using mode: 'history' which is supposed to take care of this but, I believe I am missing something. If anyone can look over my code and help me out with this, I would sure appreciate it. Thank you.
In my root directory, I have the router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
function load(componentName) {
return () => import(`#/components/${componentName}`);
}
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'Home',
component: load('Home')
}
,
{
path: '/services',
name: 'Services',
component: load('Services')
},
{
path: '/portfolio',
name: 'Portfolio',
component: load('Portfolio')
},
{
path: '/about',
name: 'About',
component: load('About')
},
{
path: '/team',
name: 'Team',
component: load('Team')
},
{
path: '/contact',
name: 'Contact',
component: load('Contact')
},
{
path: '/privacy',
name: 'Privacy',
component: load('Privacy')
},
{
path: '/success',
name: 'Success',
component: load('Success')
}
]
});
export default router;
in my source directory and within components, I have the Nav.vue component
You can see I am using a conditional that says if the index of 0 is # etc. Which I thought was supposed to eliminate the hash but it does not.
Nav.vue
<template>
<div>
<nav class="navbar navbar-expand-lg navbar-dark fixed-top" id="mainNav">
<div class="container">
<a class="navbar-brand js-scroll-trigger" href="/">
<span class="first-r">R</span>
<span class="second-r">R</span>
<span class="spark">Spark</span>
</a>
<button
class="navbar-toggler navbar-toggler-right"
type="button"
data-toggle="collapse"
data-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false"
aria-label="Toggle navigation"
>
Menu
<i class="fas fa-bars"></i>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav text-uppercase ml-auto">
<li class="nav-item" v-for="(linkObj, ind) in navList" :key="ind">
<a
class="nav-link js-scroll-trigger"
:href="linkObj.path"
v-if="linkObj.path.indexOf('#') === 0"
>{{linkObj.name}}</a>
<router-link
class="nav-link"
js-scroll-trigger
v-else
:to="linkObj.path"
>{{linkObj.name }}</router-link>
</li>
</ul>
</div>
</div>
</nav>
</div>
</template>
<script>
export default {
data: () => ({
navList: [
{
name: "Services",
path: "#services"
},
{
name: "Portfolio",
path: "#portfolio"
},
{
name: "About",
path: "#about"
},
{
name: "Team",
path: "#team"
},
{
name: "Contact",
path: "#contact"
}
]
})
};
</script>
<style lang="">
</style>
in SRC in my App.vue
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
main.js
import Vue from 'vue';
import App from './App.vue';
import router from '../router';
import VueResource from "vue-resource"
import './assets/css/style.css'
import './assets/vendor/bootstrap/css/bootstrap.min.css';
import './assets/vendor/fontawesome-free/css/all.min.css';
Vue.config.productionTip = false
Vue.use(VueResource);
new Vue({
router,
render: h => h(App),
}).$mount('#app')
For the sake of space, I will only show the minimum of the Home.vue component
<template>
<div>
<Nav></Nav>
<div>
<header class="masthead">
<div class="container">
<div class="masthead-holder">
<img src="../assets/img/RRLogo2.png" class="mt-5 logo" alt />
<div class="intro-text">
<div class="intro-lead-in">Welcome RR Spark Web Studio!</div>
<div class="intro-heading text-uppercase">Hire Us For Your Next Web Project</div>
<a
class="btn tell-me btn-xl text-uppercase js-scroll-trigger"
href="#services"
>Tell Me More</a>
</div>
</div>
</div>
</header>
</div>
<div>
<section class="page-section" id="services">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Services</h2>
<h3 class="section-subheading text-muted">Delivering your ideal website is what we do.
</h3>
</div>
</div>
<div class="row text-center">
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x icon-color"></i>
<i class="fas fa-paint-brush fa-stack-1x fa-inverse"></i>
</span>
<h4 class="service-heading">Web Design</h4>
<p
class="text-muted text-justify"
>We take the time to really know your company to get a better understanding of your goals, vision and where you want to go in order to create that perfect, unique website that will help you reach your specific target audience. Your site needs to be user friendly, functional and visually pleasing and that is where are designer comes in to ensure the design is exactly what you want.</p>
</div>
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x icon-color"></i>
<i class="fas fa-globe fa-stack-1x fa-inverse"></i>
</span>
<h4 class="service-heading">Web Development</h4>
<p
class="text-muted text-justify"
>Working hand in hand with our designer, we use the latest in web technology, such as HTML; CSS3; JavaScript, to bring you a site that is fast, SEO (Search Engine Optimization) friendly as well as highly responsive. Keeping up with the latest Standards from W3C, your project will be built for a superior user experience on mobile, tablet as well as PC.</p>
</div>
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x icon-color"></i>
<i class="fas fa-laptop fa-stack-1x fa-inverse"></i>
</span>
<h4 class="service-heading">Content Management</h4>
<p
class="text-muted text-justify"
>Keeping up with changes on your site in this fast-paced world means that your site will constantly need new and fresh content. Using a content management system (CMS), such as WordPress allows you, the user, the ability to quickly add new content whenever you wish, maintain and implement marketing campaigns and monitor analytics all from within the user dashboard. We provide either WordPress modifications or Custom theming solutions.</p>
</div>
</div>
<div class="row mt-5">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Ready? So Are We</h2>
<div class="row">
<div class="col-lg-6 p-5">
<h3
class="section-subheading text-muted mb-4"
>For a no-obligation quote, click the button below and tell us all about your project.</h3>
<a
class="btn tell-me btn-xl mt-0 mb-4 text-uppercase js-scroll-trigger"
href="#contact"
>CLICK ME</a>
</div>
<div class="col-lg-6 p-5">
<h3
class="section-subheading text-muted mb-4"
>If you don't like contact forms, ask us a question via WhatsApp using the button below.</h3>
<a
class="btn tell-me btn-xl mt-0 text-uppercase js-scroll-trigger"
href="https://api.whatsapp.com/send?phone=525517043338"
target="_blank"
>WhatsApp</a>
</div>
</div>
<!--end row CTA-->
</div>
</div>
</div>
</section>
</div>
<!--other sections-->
<div>
<section class="page-section" id="contact">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Contact Us</h2>
<h3
class="section-subheading text-white"
>We are ready to get your project up and live.</h3>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<Contact></Contact>
</div>
</div>
</div>
</section>
</div>
<Footer></Footer>
</div>
</template>
<script>
import Nav from "./Nav";
import Footer from "./Footer";
import Contact from "./Contact";
export default {
name: "App",
components: {
Nav,
Contact,
Footer
},
data: () => ({
portfolioJSON: [
{
linkToModal: "#portfolioModal1",
image: require("../assets/img/portfolio/icoimanismall.jpg"),
header: "Imani Luz Y HarmonÃa",
caption: "Graphic Design"
},
{
linkToModal: "#portfolioModal2",
image: require("../assets/img/portfolio/icopsicliliansmall.jpg"),
header: "Transformando Vidas",
caption: "Web Development"
},
{
linkToModal: "#portfolioModal3",
image: require("../assets/img/portfolio/icopsicovintsmall.jpg"),
header: "Viaje a tu Interior",
caption: "Web Development"
},
{
linkToModal: "#portfolioModal4",
image: require("../assets/img/portfolio/algovisolutionsthumb.png"),
header: "Algovi Solutions Landing Page",
caption: "Web Development"
},
{
linkToModal: "#portfolioModal5",
image: require("../assets/img/portfolio/naplesdentalthumb.png"),
header: "Naples Dental Clinic",
caption: "Web Development"
},
{
linkToModal: "#portfolioModal6",
image: require("../assets/img/portfolio/xploreflythumb.png"),
header: "Xplorefly Travel Agency",
caption: "Web Development"
}
]
})
};
</script>
<style lang="">
</style>
Again, thank you for looking at this as I am new to VueJS and learning.
In Nav.vue your navList array containing the paths has a #. The paths has to be identical with the path you defined on your router.js. Try changing your array
from:
navList: [
{
name: "Services",
path: "#services"
},
{
name: "Portfolio",
path: "#portfolio"
},
{
name: "About",
path: "#about"
},
{
name: "Team",
path: "#team"
},
{
name: "Contact",
path: "#contact"
}
]
to
navList: [
{
name: "Services",
path: "/services"
},
{
name: "Portfolio",
path: "/portfolio"
},
{
name: "About",
path: "/about"
},
{
name: "Team",
path: "/team"
},
{
name: "Contact",
path: "/contact"
}
]
if you can't modify the array try modifing the string like:
<router-link :to="linkObj.path.replace('#','/')">{{ linkObj.name }}</router-link>
Conclusion
For routing Navigation to render other components in the <router-view /> tag, try to use exclusive the <router-link to="/path"> tag. The <a href="/path"> tag will also navigate you but this will reload the page.
If you want to navigate to anchors in your page you can still use the <a href="#anchorId">
it bcs your tag contain # in a href="#".
its not part VueRouter, you need to remove it and make new function to scroll to specific area or div.
let say:
<a class="btn tell-me btn-xl text-uppercase js-scroll-trigger" href="#services">Tell Me More</a>
You need to remove the '#' inside the 'href' then add #click="goToSection('yourAreaWantToJump')"
then need to add scroller to specific div. in this case u need to scroll to section 'services'
This is my first proper Vue JS project...I've searched Stack O and can't find anything that specifically addresses my issue, but I apologise if this has already been asked.
The Problem
I have two components, the parent is the page layout, while the child is a module of that page. When somebody clicks on a button in the child component, I want to trigger a function in the parent component.
The $emit part of the process is firing correctly according to the VueJS Dev Tools, but the function in the parent doesn't trigger.
Where am I going wrong?
Here is my abberviated code (I've removed anything not related to the issue)...
Parent Component
<template>
<div :toggle-nav="showHideNav" :class="navState">
<div class="wrapper">
<layout-toolbar></layout-toolbar>
</div>
</div>
</template>
<script>
import layoutToolbar from '~/components/layout/toolbar.vue'
export default {
components: {
layoutToolbar,
},
data: function() {
return {
navState: 'menu-closed'
}
},
methods: {
showHideNav: function(event) {
if (this.navState == 'menu-closed') {
this.navState = 'menu-open'
} else {
this.navState = 'menu-closed'
}
}
}
}
</script>
Child Component
<template>
<div class="toolbar" role="navigation">
<div class="tools" role="group">
<button
class="button-icon"
aria-label="Open Navigation"
#click="$emit('toggle-nav')">
<i aria-hidden="true" class="far fa-bars" title="Open Navigation"></i>
<span class="sr-only">Open Navigation</span>
</button>
</div>
</div>
</template>
Do I need to be making use of props?
Appreciate any help that can be offered.
You have to pass a function as a prop and then emit that function from your child component. So your parent template looks like this .
<template>
<div :toggle-nav="showHideNav" :class="navState">
<div class="wrapper">
<layout-toolbar #showHideNav="showHideNav"></layout-toolbar>
</div>
</div>
</template>
And your child template looks like this
<template>
<div class="toolbar" role="navigation">
<div class="tools" role="group">
<button
class="button-icon"
aria-label="Open Navigation"
#click="$emit('showHideNav',<pass-your-event-or-data>)">
<i aria-hidden="true" class="far fa-bars" title="Open Navigation"></i>
<span class="sr-only">Open Navigation</span>
</button>
</div>
</div>
</template>
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?
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.