Vue-router named views not working as expected - vue.js

I'm stuck on getting my Vue-router to work as I want. I have two <route-view /> Named Views in my templates. One is the main view for navigation on the top nav bar, which is working fine. The other doesn't work. I have defined my router as such (shortened for clarity):
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
const routes = [
{
path: '/',
name: 'home',
components: { main: HomeView },
},
{
path: '/bpvapp',
name: 'bpvapp',
components: { main: () => import('../views/BpvApp.vue') },
},
{
path: '/bpvapp/projects',
name: 'projects',
components: { bpvapp: HomeView }, // HomeView component for testing purposes
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
My App.vue has the main route defined in the template:
<template>
<div class="main-container">
<navigation-bar
:isLoggedIn="isLoggedIn"
:profileImageUrl="userImage"
:signOut="signOut"
/>
<suspense>
<router-view name="main" />
</suspense>
</div>
</template>
My BpvApp.vue component, which is a CSS Grid layout, uses the second route view (shortened for clarity):
<template>
<div class="bpv--app-container">
<div class="bpv--app-toolbar">TOOLBAR</div>
<div class="bpv--app-sidebar">
<bpv-button
text="Projects"
icon="folder-star-multiple"
styling="info"
class="bpv--bs-1"
:active="activeSection === 'projects'"
to="/bpvapp/projects"
/>
</div>
<div class="bpv--app-content">
<suspense>
<router-view name="bpvapp" />
</suspense>
</div>
<div class="bpv--app-assist">
RIGHT Lorem ipsum dolor sit amet consectetur, adipisicing elit. Laboriosam
placeat deserunt quidem fugiat dicta repellat nobis mollitia! Consectetur
laudantium dolor, odio adipisci at qui, deserunt minus facere rerum,
voluptates maiores.
</div>
<div class="bpv--app-footer">FOOTER</div>
</div>
</template>
The div with class bpv--app-content should show the content from the component I push to the router. You can see the button will push to /bpvapp/projects, which in my router definition should invoke the router with name bpvapp and show the content.
For the sake of completeness, my button component handles the to prop as follows:
...
methods: {
// this.to has the correct value '/bpvapp/projects', I double-checked
buttonClicked(e) {
if (this.to) {
this.$router.push(this.to);
} else if (this.action) {
this.action();
} else {
this.$emit('click', e);
}
},
},
...
What happens now is that I get a blank screen. When I inspect the HTML, the entire structure is not there, except the nav bar.
What am I doing wrong here? I've tried removing the <suspense> tag and loading static content, but that made no difference.

One issue is the two routes are configured as siblings, but the second one should be a nested child route of the first, using the route's children option:
// router.js
const routes = [
{
path: '/',
name: 'home',
components: { main: HomeView },
},
{
path: '/bpvapp',
name: 'bpvapp',
components: { main: () => import('#/views/BpvApp.vue') },
👇
children: [
{
path: 'projects',
name: 'projects',
components: { bpvapp: () => import('#/views/BpvProject.vue') },
},
],
},
]
Note: When there's only one router-view at a particular level, you don't need to name it.
The other issue is you don't need <suspense> for this. <suspense> is intended for async components declared with an async setup() option or within defineAsyncComponent() (which is not appropriate for a route's components option). While the router components are technically loaded asynchronously via the dynamic imports, they're not actually the async components that <suspense> expects.
demo

Related

Vue 3 - Component is not loaded or rendering

I have following component:
<script setup>
import {defineProps, ref, watch} from "vue";
import ProductsComponent from '#/components/Products.vue'
import OrdersComponent from '#/components/Orders.vue'
import {useTableOrderStore} from "#/store/tableOrder";
const tableOrderStore = useTableOrderStore()
const props = defineProps({
orderID: {
type: Number
},
tableID: {
type: Number
},
tableName: {
type: String
}
})
let orders = ref([])
watch(props, (newProp, oldProp) => {
orders = tableOrderStore.tableOrders
console.log(orders)
})
</script>
<template>
<products-component :tableName="tableName"></products-component>
<orders-component v-for="order in orders" :order="order" :key="order.id"></orders-component>
</template>
And OrdersComponent which is loaded in this component:
<script setup>
import {watch} from "vue";
let props = defineProps({
order: {
type: Array,
required: true
}
})
watch(props, (newProp, oldProp) => {
console.log(newProp)
})
console.log(12333)
</script>
<template>
<div class="row">
{{ order }}
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, expedita?
</div>
</template>
When the main component is shown, ProductsComponent is loaded and shown, but OrdersComponent is not. What am I doing wrong here?
The component is not rendered because the array orders is still empty and the watcher to update it is not working properly which should be written by returning props from callback and adding other options (immediate and deep):
let orders = ref([])
watch(()=>props, (newProp, oldProp) => {
orders.value = tableOrderStore.tableOrders
console.log(orders.value)
},{
immediate:true,
deep:true //since props are an object
})

VUE JS: How to keep same structure in whole project but different header and footer for each page

I am using router-view. In App.vue I want this structure so in every other pages I can have different header, footer but overall structure:
<router-view>
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
</router-view>
In home page component I want to do something like this:
<slot name="header">header content</slot>
<slot name="footer">footer content</slot>
Othercontent here....
In about us page component I want to do something like this:
<slot name="header">about header content</slot>
<slot name="footer">about footer content</slot>
Othercontent here....
how can I achive this?
The solution goes around layouts and sub-pages where layouts are parents and related pages placed as children.
Consider these two layouts:
// src/layouts/PublicLayout.vue
<template>
<section class="public-layout">
<router-view/>
</section>
</template>
<script>
export default {
name: 'PublicLayout',
};
</script>
and
// src/layouts/MainLayout.vue
<template>
<section class="main-layout">
<!-- header -->
<router-view/>
<!-- footer -->
</section>
</template>
<script>
export default {
name: 'MainLayout',
};
</script>
Now, you can tune routes in your flavor:
// src/router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
const publicRoutes = [
{
path: '/public',
component: () => import('layouts/PublicLayout.vue'),
children: [
{
path: 'login',
name: 'Login',
component: () => import('pages/ULogin.vue'),
},
{
path: 'sign-up',
name: 'SignUp',
component: () => import('pages/USignUp.vue'),
},
],
},
];
const mainRoutes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{
path: '',
name: 'Home',
component: () => import('pages/UHome.vue'),
},
{
path: 'profile',
name: 'Profile',
component: () => import('pages/Profile.vue'),
},
],
},
];
const Router = new VueRouter({
routes: mainRoutes.concat(publicRoutes),
});
// snippet skipped
Here, MainLayout and PublicLayout play the structural role while other pages are placed as their children. By navigating to each page (e.g. from /login to /profile) the proper layout will get loaded as well.
<your-base-layout>
<slot #header>about header content</slot>
<slot #footer>about footer content</slot>
</your-base-layout>
its called "Named Slot"

Update the parent data when user navigates to a specific route path

I'm new in VueJs, trying to set up a web application with Vue-route, and want to update the <header> style when user navigates to a specific URL, whether using "URL bar" directly or "navigation bar". In this case, we have a parent component that contains height_status data and some <router-links> on template.
I've done the "navigation bar" part with $emit technique and it works well but then I've tried to use it on created lifecycle hook in order to update the header whenever the /home route is created but event listener will not reach the parent_component.
How can I solve this? Is there a better way to do that?
Please see the code below:
Parent_component.vue
<template>
<div id="app">
<router-link to="/home" #height_size_ctrl="change_height">Home</router-link>
<router-link to="/about">About us</router-link>
<router-link to="/contact">Contact us</router-link>
<header :class="height_status ? 'head-h-s' : 'head-h-m'"></header>
<router-view/>
</div>
</template>
<script>
export default {
name: "Parent_component"
},
data() {
return {
height_status: false
}
},
methods: {
change_height(h) {
this.height_status = h
}
}
}
</script>
router.js
Vue.use(Router)
export default new Router({
routes: [
{
path: '/home',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: about
},
{
path: '/contact',
name: 'contact',
component: contact
}
]
})
home.vue
<template>
<h1>hello</h1>
</template>
<script>
export default {
name: 'home',
created: function(){
return this.$emit("height_size_ctrl", true)
}
}
</script>
You could also change the router:
router.js
{
path: '/home',
name: 'home',
component: Home,
meta: {
headerClass: 'head-h-s'
}
}
In your component
Parent_component.vue
computed: {
headerClass() {
return this.$route.meta.headerClass
}
}
Now headerClass is available in the template.
<header :class="headerClass"></header>
why don't you try class binding on route or route name something like:
<div :class="{'height_status': this.$route == '/home'}">Header</div>
or
<div :class="{'height_status': this.$route.name == 'Home'}">Header</div>
As #kcsujeet said, class binding is the good way we can do this. In this case we need to look at the condition this.$route.path. if value is equal to the /home select 'head-h-m, otherwise select .head-h-s.
<header class="head-sec" :class=" this.$route.path == '/home' ? 'head-h-m' : 'head-h-s'">
Also we're able to access other route defined properties using this.$route. I suggest take a look at the router.js file.
routes: [
{
path: '/home',
name: 'home',
component: Home
}

How to remove # from the url bar in framework7-vue

I'm using framework7 with vuejs i.e framework7-vue. Everything is working good. When doing the routes for browser i used pushState="true" and pushStateSeperator="", This removes the "#!" from the url bar but the problem is when i visit the url lets say localhost:8080/about it gives me an error cannot get /about.
Now if i don't set pusStateSeperator to " ", it would work fine the url would turn to http://localhost:8080/#!/about and now when i hit the same url from browser directly the page loads without error.
So any solution how to remove the "#!" and make links work ?
i want my link to work like this localhost:8000/about and if i reload it should not give me error "Cannot get /about"
routes.js
import HomePage from './pages/home.vue';
import AboutPage from './pages/about.vue';
import TermPage from './pages/terms.vue';
import FormPage from './pages/form.vue';
import DynamicRoutePage from './pages/dynamic-route.vue';
import NotFoundPage from './pages/not-found.vue';
import PanelLeftPage from './pages/panel-left.vue';
import PanelRightPage from './pages/panel-right.vue';
import ViewSchool from './pages/school/viewschool.vue'
export default [
{
path: '/',
component: HomePage,
},
{
path: '/panel-left/',
component: PanelLeftPage,
},
{
path: '/panel-right/',
component: PanelRightPage,
},
{
path: '/about',
component: AboutPage,
},
{
path: '/terms/',
component: TermPage,
},
{
path: '/form/',
component: FormPage,
},
{
path: '/dynamic-route/blog/:blogId/post/:postId/',
component: DynamicRoutePage,
},
{
path: '/viewschool/:school_id',
component:ViewSchool,
props:true,
},
{
path: '(.*)',
component: NotFoundPage,
},
];
app.js
// Import Vue
import Vue from 'vue';
// Import F7
import Framework7 from 'framework7/framework7.esm.bundle.js';
// Import F7 Vue Plugin
import Framework7Vue from 'framework7-vue/framework7-vue.esm.bundle.js';
// Import F7 Styles
import Framework7Styles from 'framework7/css/framework7.css';
// Import Icons and App Custom Styles
import IconsStyles from './css/icons.css';
import AppStyles from './css/app.css';
// Import App Component
import App from './app.vue';
// Init F7 Vue Plugin
Framework7.use(Framework7Vue)
// Init App
new Vue({
el: '#app',
template: '<app/>',
// Register App Component
components: {
app: App
}
});
app.vue
<template>
<!-- App -->
<f7-app :params="f7params">
<!-- Statusbar -->
<f7-statusbar></f7-statusbar>
<!-- Left Panel -->
<f7-panel left reveal>
<f7-view url="/panel-left/"></f7-view>
</f7-panel>
<!-- Right Panel -->
<f7-panel right cover theme-dark>
<f7-view url="/panel-right/"></f7-view>
</f7-panel>
<!-- Main View -->
<f7-view id="main-view" url="/" main ></f7-view>
<!-- Popup -->
<f7-popup id="popup">
<f7-view>
<f7-page>
<f7-navbar title="Popup">
<f7-nav-right>
<f7-link popup-close>Close</f7-link>
</f7-nav-right>
</f7-navbar>
<f7-block>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque, architecto. Cupiditate laudantium rem nesciunt numquam, ipsam. Voluptates omnis, a inventore atque ratione aliquam. Omnis iusto nemo quos ullam obcaecati, quod.</f7-block>
</f7-page>
</f7-view>
</f7-popup>
<!-- Login Screen -->
<f7-login-screen id="login-screen">
<f7-view>
<f7-page login-screen>
<f7-login-screen-title>Login</f7-login-screen-title>
<f7-list form>
<f7-list-item>
<f7-label>Username</f7-label>
<f7-input name="username" placeholder="Username" type="text"></f7-input>
</f7-list-item>
<f7-list-item>
<f7-label>Password</f7-label>
<f7-input name="password" type="password" placeholder="Password"></f7-input>
</f7-list-item>
</f7-list>
<f7-list>
<f7-list-button title="Sign In" login-screen-close></f7-list-button>
<f7-block-footer>
<p>Click Sign In to close Login Screen</p>
</f7-block-footer>
</f7-list>
</f7-page>
</f7-view>
</f7-login-screen>
</f7-app>
</template>
<script>
// Import Routes
import router from './routes.js'
export default {
data() {
return {
// Framework7 parameters here
f7params: {
id: 'io.framework7.testapp', // App bundle ID
name: 'Framework7', // App name
theme: 'auto', // Automatic theme detection
// App routes
routes: router,
view:
{
pushState:"true",
}
},
}
}
}
</script>
The first link where #! is coming and things work fine even when i refresh page
Now this happens when i set pushStateSeperator="", now when u refresh the link you get this error.
I don't know if you have found the solution already. For anyone else looking for this:
if your app lies at https://example.com/myapp
<f7-view id="main-view" main url="/"
:push-state="true"
push-state-separator="/myapp"
></f7-view>
push-state-separator is the key here whose default value is #!/
Try specifying the router mode to 'history';
let router = new Router({
mode: 'history',
routes: [
{
name: 'About',
path: '/about',
component: About
}
]
})

Vue Router moves to route but loads wrong component

I have the following router config:
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'notselected',
component: PackageUnselected
},
{
path: '/package/:id',
children: [
{ path: 'meta', name: 'packageMeta', component: ViewPackageMeta },
{ path: 'readme', name: 'packageReadme', component: PackageReadme },
{ path: 'docs', name: 'packageDocs', component: PackageDocs },
{
path: 'playground',
name: 'packagePlayground',
component: PackagePlayground
}
]
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '*',
redirect: '/'
}
]
});
And when I'm at the root route it correctly identifies the route name as notselected. When I route to any of the "/package/[id]" routes though it continues to load the PackageUnselected component instead of the appropriate route (aka, ViewPackageMeta, PackageDocs, etc.).
Now at the point in the DOM where I want the route to insert the route's component I have the following template:
<v-tab-item v-for="item in tabs" :id="'tab-item-' + item" :key="item" exact>
item: {{item}}
<router-view :selectedPackage="selected"></router-view>
</v-tab-item>
And because I have installed vuex-router-sync it's easy to see the route state at any given time. So when clicking on the route that should load PackageDocs:
But the component view window of vue-devtools looks like this:
the highlighted area shows that NO component has been loaded into the tabs. I then tried adding a component to the definition of the parent route /package/:id:
{
path: '/package/:id',
component: Packages,
children: [
{ path: 'meta', name: 'packageMeta', component: ViewPackageMeta },
{ path: 'readme', name: 'packageReadme', component: PackageReadme },
{ path: 'docs', name: 'packageDocs', component: PackageDocs },
{
path: 'playground',
name: 'packagePlayground',
component: PackagePlayground
}
]
},
I then had to create the world simplest component for Packages:
<template>
<view-router-view></view-router-view>
</template>
This results in the following:
Hmmm. Can't figure out what to do next. Anyone have any pointers?
When I route to any of the "/package/[id]" routes though it continues
to load the PackageUnselected component instead of the appropriate
route (aka, ViewPackageMeta, PackageDocs, etc.).
That is the correct behavior of the vue-router.
Children can only be loaded when URL paths are:
/package/[id]/meta
/package/[id]/readme
/package/[id]/playground
/package/[id]/docs
Parent path may not have component defined if you have configured redirect option and your user, who opens /package/[id] will be redirected to your default path (could be anything).
Lets move to the next part of your question...
<v-tab-item v-for="item in tabs" :id="'tab-item-' + item" :key="item" exact>
item: {{item}}
<router-view :selectedPackage="selected"></router-view>
</v-tab-item>
You don't need to create here 4 different <router-view> tags, you just need one where all your children components will display html code.
<v-tab-item v-for="item in tabs" :id="'tab-item-' + item" :key="item" exact></v-tab-item>
<router-view></router-view>
Now you will have only one router-view and it's the default one. When user clicks on any of your tabs you just need to this.$router.push a new path on #click-event in Packages component. That's it.
I have created a simple example (codepen) to demonstrate how this task can be solved:
Vue.use(VueRouter);
// Components
let MetaPackages = {
mounted() { console.log('Mounted MetaPackages...'); },
template: `<div>MetaPackages...</div>`,
};
let DocsPackages = {
mounted() { console.log('Mounted DocsPackages...'); },
template: `<div>DocsPackages...</div>`,
};
let ReadmePackages = {
mounted() { console.log('Mounted ReadmePackages...'); },
template: `<div>ReadmePackages...</div>`,
};
let Packages = {
mounted() { console.log('Mounted Packages... ' + this.$route.path); },
template: '<div>Packages (parent) screen...<br/><router-view></router-view></div>',
};
// Router
const router = new VueRouter({
mode: 'hash',
routes: [
{
path: "/packages/:id",
component: Packages,
children: [
{path:"meta", component: MetaPackages},
{path:"docs", component: DocsPackages},
{path:"readme", component: ReadmePackages}
]
}
]
});
// Vue instance
const vm = new Vue({
el: '#app',
router,
components: {Packages, MetaPackages, DocsPackages, ReadmePackages}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.2/vue-router.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<router-link to="/packages/100/meta">Meta</router-link>
<router-link to="/packages/100/docs">Docs</router-link>
<router-link to="/packages/100/readme">Readme</router-link>
<hr/>
<router-view></router-view>
</div>