How to replace parent view with child route component in Vue - javascript

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>

Related

pass component according to v-if - Vue.JS

I am trying to create a parent component with all kinds of graphics, and depending on what I need to pass on a specific graphic (raise a child) to another component, but I don't necessarily know how I would dynamically do it for the user.
my html charts (chart.vue)
<template>
<div >
<bars v-if="type == 'bar'">
<div class="row gutter-sm">
<div class="col-md-3">
<apexchart ref="chart1" type="bar" :options="options" :series="updatedData"></apexchart>
</div>
</div>
<div class="col-md-3">
<apexchart ref="chart3" type="bar" :options="options3" :series="updatedData"></apexchart>
</div>
</bars>
<lines v-if="type == 'line'">
<div class="row gutter-sm">
<div class="col-md-3">
<apexchart ref="chart4" type="line" :options="options4" :series="updatedData"></apexchart>
</div>
</div>
</lines>
</div>
<template>
If I wanted to pass on bar graphics to my menu.vue, would it be like this?
my html menu
<template>
<div >
<table class="data-table-2" style="min-width: 800px">
<charts type="bar"/>
</table>
</div>
<template>
scripts menu
<script>
import charts from "./charts.vue"
export default {
name: 'menu',
components: {
charts
}
}
</script>
The way you pass values from the parent component to the child component is wrong
<template>
<div >
<table class="data-table-2" style="min-width: 800px">
<charts :type='bar'/>
<charts :type='line'/>
<charts />
</table>
</div>
<template>
child component receiving value, and you can set a default value:
// init in child component
props: {
type: {
type: String,
default: 'bar' // set a default value
}
},
data(){
...
}
then you can use type in child component
<template>
<div >
<bars v-if"type == 'bar'">
<div class="row gutter-sm">
<div class="col-md-3">
<apexchart ref="chart1" type="bar" :options="options" :series="updatedData"></apexchart>
</div>
</div>
<div class="col-md-3">
<apexchart ref="chart3" type="bar" :options="options3" :series="updatedData"></apexchart>
</div>
</bars>
<lines v-if"type == 'line'">
<div class="row gutter-sm">
<div class="col-md-3">
<apexchart ref="chart4" type="line" :options="options4" :series="updatedData"></apexchart>
</div>
</div>
</lines>
</div>
<template>
You need to define a Props in child component.
//Child Component Script
export default ChildComponent {
data() {
return {}
},
props: {
type: string,
}
}
In parent component you need to call child component and pass type data like this
//parent component
<template>
<div>
<child-component :type="line"/>
</div>
</template>

Nesting a component inside another component without nesting it in the routes

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.

Why is my nested child component not rendering the view?

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.

Vue.js 2 Route Not Working. Posts Variable not Accessible

First project in Vue.js. Working with WP REST API.
I am able to get all my posts to show, but as soon as I try to implement the Vue-router my component that shows all the posts, home-post-list, dies at the first v-if="posts" statement.
Clearly, Vue thinks there is no posts so it isn't rendering anything else, but I cannot figure out how to make it recognize posts. I don't get any errors in the console.
WhenI look at the Vue DevTools, I see:
https://www.dropbox.com/s/5441k1kw8ocmzad/Screenshot%202018-05-22%2010.43.24.png?dl=0
So, the router-view appears to be working properly, but props is empty. I thought I was passing props from the main instance to the child component but maybe I am doing something wrong here.
I will show you my current code.
***HomePostList component
const HomePostList = Vue.component('home-post-list', {
props:['posts'],
template: `<div class="cell medium-8">
<div id="all-posts" class="all-posts" v-if="posts">
<div class="grid-x grid-margin-x">
<div class="post medium-6 cell" :class="{'medium-12':index===0}" v-for="(post,index) in posts">
<div class="img-bg" :class="{'first-post':index === 0}" :style="'background-image: url(' + post._embedded['wp:featuredmedia']['0'].source_url + ')'"></div>
<aside class="post-meta grid-x">
<div class="cell small-12">
<h3>{{ post.title.rendered | limitWords(6) }}</h3>
</div>
<div class="cell small-6">
<div class="post-category" v-for="(category,index) in post.cat_name.slice(0,1)">
<a :href="'/category/' + category.slug">{{ category.cat_name }}</a>
</div>
</div>
<div class="cell small-6">
<p><i class="fal fa-calendar-alt"></i> {{ post.date | parseTime }}</p>
</div>
</aside>
</div>
</div>
</div>
</div>`
});
***SinglePost Component
const SinglePost = Vue.component('single-post-template', {
props:['posts'],
template: `<div class="cell medium-8">
<div class="grid-x grid-margin-x">
<p>Single Post here</p>
</div>
</div>`
});
***Routes & Vue Instance
const routes = [
{
path: '/',
component: HomePostList,
props: true
},
{
path: '/post/:postId',
name: 'post',
component: SinglePost
}
];
const router = new VueRouter({
routes
});
new Vue({
el: '#app',
router,
data() {
return{
posts: [],
searchTerm:'',
searchPosts:[],
currentRoute: window.location.pathname
}
},
created (){
var $this = this;
axios
.get(apiRoot + 'posts?_embed')
.then(function (response) {
$this.posts = response.data;
}
)
},
methods: {
loadMorePosts: function(){
var $this = this;
axios
.get(apiRoot + 'posts?_embed')
.then(function (response) {
$this.posts = response.data;
}
)
},
},
computed:{
},
});
***index.php
<?php
/*
Template Name: Front
*/
get_header(); ?>
<!-- Home Page -->
<div class="grid-container">
<div class="grid-x grid-margin-x">
<!-- Main Post Container -->
<router-view></router-view>
<!-- Sidebar -->
<div id="sidebar" class="cell medium-4">
<sidebar-search></sidebar-search>
</div>
</div>
</div>
<?php get_footer();
I got this working by adding posts to the router-view element.
<router-view :posts="posts"></router-view>
Not sure if this is the correct way to do this, but it works.

Dynamically add a child component in Vue JS

I need some help in Vue JS and Laravel with adding a child vue component.
I have a parent component called "wrapper" and some child components called like "show-1", "show-2", "show-3" ... etc.
Parent component:
<template>
<div class="card border-light">
<div class="card-header">
<h5 class="title">{{ title }}</h5>
</div>
<div class="card-body">
<component
is="view"
></component >
</div>
<div class="card-footer"></div>
</div>
</template>
<script>
export default {
props : ['title'],
data() {
return {
view : ''
}
}
}
</script>
An exmaple child component like "show-1":
<template>
<div> show-1 </div>
</template>
This code below is in blade for rendering wrapper component with a dynamic child component name:
<wrapper
title="Example"
view="show-1"
></wrapper>
This code is not working but if i change the parent view data "show-1" instead of empty, it works. why ?
When I change the view prop, child vue component should be changed too. How could I do this ?
I want to pass the view attribute to parent component dynamically.
You can use :is attribute. You can read more about it here:
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
You can use the same mount point and dynamically switch between
multiple components using the reserved element and
dynamically bind to its is attribute....
<template>
<div class="card border-light">
<div class="card-header">
<h5 class="title">{{ title }}</h5>
</div>
<div class="card-body">
<!-- make sure to use : -->
<component v-if="view" :is="view"></component >
</div>
<div class="card-footer"></div>
</div>
</template>
<script>
export default {
props : ['title'],
data() {
return {
view : ''
}
}
}
</script>
#Eduardo has the right answer. To add to it, import your components into the parent and switch between them via a data property:
...
<component :is="current"></component >
...
data: {
current: 'show1'
},
components: {
show1: Show1Component,
show2: Show2Component,
show3: Show3Component
}
The key is to bind the component using the name of the dynamic component.
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components

Categories