new to Vue and frontend dev in general.
I'm trying to make a universal nav bar in Vue Router using bootstrap vue with a search bar implemented.
However, because I have my nav bar placed in App.vue, it is unable to pass the search function to specific routes.
Here is what I have in my App.vue
<div id="app" >
<b-navbar toggleable="lg" variant="light" type="light" class="justify-content-between">
<b-navbar-brand><router-link to="/">Brand</router-link></b-navbar-brand>
<b-collapse class="nav-collapse" is-nav>
<b-nav-item><router-link to="/about">Route 1</router-link></b-nav-item>
</b-collapse>
<b-navbar-nav class="ml-auto">
<b-nav-form>
<b-form-input class="form-control mr-sm-2" v-model="search_term" placeholder="Search..."></b-form-input>
<b-button variant="outline-success my-2 my-sm-2" type="submit" v-on:click="getSearch(search_term)">Search</b-button>
</b-nav-form>
</b-navbar-nav>
</b-navbar>
<router-view/>
</div>
</template>
<script>
</script>
<style>
</style>
The router functions are in its own class
As you can see the search stuff I have implemented under b-nav-form is from when the navbar was in a specific page where the functions are implemented.
However, with a navbar in individual pages, it has to be re-rendered every time the user re-routes. So I put it in the App.vue page, where it is permanently rendered.
How can I pass the search_term to its specific function in its specific page while the navbar being universal? Is that possible? Or is it easier to just keep the navbar in its own page.
The best approach to share a navbar accross your app and using its data through any route component is to keep it at the App.vue level as you thought.
The tricky part is then to access the search in the route components, but that pretty easy using a Vuex store.
The Vuex store is a centralised source of truth for your app. Any component, as deeply burried in your app structure as imaginable, can access it through this.$store or other API (Documentation)
All you have to do is store your search value in it and let all your component access it as needed.
Minimal example below:
const mainComponent = Vue.component("mainComponent", {
computed: {
search() {
return this.$store.state.search
}
},
template: "<div><div>Main component</div><div>Search: {{ search }}</div><div><router-link to='/other'>Go to Other</router-link></div></div>"
});
const otherComponent = Vue.component("otherComponent", {
computed: {
search() {
return this.$store.state.search
}
},
template: "<div><div>Other component</div><div>Search: {{ search }}</div><div><router-link to='/'>Go to Main</router-link></div></div>"
});
const store = new Vuex.Store({
state: {
search: ''
},
mutations: {
search(state, payload) {
state.search = payload;
}
}
});
const router = new VueRouter({
routes: [{
path: "*",
redirect: "/"
},
{
path: "/",
component: mainComponent
},
{
path: "/other",
component: otherComponent
}
]
});
new Vue({
el: "#app",
store,
router,
data: {
searchValue: ''
},
methods: {
submitSearch() {
this.$store.commit("search", this.searchValue);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.5.1/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.4.8/vue-router.min.js"></script>
<div id="app">
<div class="search-bar">
<form #submit.prevent="submitSearch">
<input type="text" v-model="searchValue" placeholder="search" />
<input type="submit" value="search" />
</form>
</div>
<router-view />
</div>
Related
I have created a sort of First page for my project, but I don't know why it renders everything twice on my page:
here is my First.vue component that is used by the router as the first page:
<template>
<h1>Bienvenue</h1>
<router-view></router-view>
<div class="routing" v-if="this.$route.path == '/'">
<router-link to='/app'>Go to application</router-link>
<br>
<router-link to='/test'>Go to test</router-link>
</div>
</template>
and here is what I get on the page when I npm run serve
Does anyone knows where it comes from?
UPDATE
When I delete the router-view element, the components appear once but when I click on one of the links, it changes the URL of the page but the page in itself is not showing the component.
And when I try to put everything in my router-view, like this:
<template>
<router-view>
<div class="routing" v-if="this.$route.path == '/'">
<h1>Bienvenue</h1>
<router-link to='/app'>Go to application</router-link>
<br>
<router-link to='/test'>Go to test</router-link>
</div>
</router-view>
</template>
it appears once, but like the other case, when I click on a link, it is just changing the URL and not the page.
Here is my index.js to show you how my routes are defined:
import {createRouter, createWebHistory} from 'vue-router'
import App from '../App.vue'
import Test from '../Views/Test.vue'
import First from '../Views/First.vue'
export const router = createRouter({
history: createWebHistory(),
routes: [
{
path:'/',
name: 'First',
component: First,
},
{
path:'/test',
name: 'Test',
component: Test
},
{
path:'/app',
name: 'App',
component: App
}
]
})
Just to clarify what results I expect from my app:
When I run the project, I want to launch on a page with just a header saying 'Hello' at the top of the page and two links where I can either click on 'Go to the application' or 'Go to test'.
Then, when I click on one of the links, I want to see the content of the component (so either test or the app), but I don't want to see the header and the links anymore.
I am not sure but try this:
{
path: "/app",
component: () => import("../App.vue"),
name: "App"
},
{
path: "/test",
component: () => import("../Views/Test.vue"),
name: "Test"
},
{
path: "/",
component: () => import("../Views/First.vue"),
name: "First"
}
Update with sample code or you can refer to live code here.
// App.vue
const First = {
template: `<div class = "container" id="app">
<h1>Hi from First</h1>
<hr>
<router-link to="/foo">Foo</router-link>
<router-link to="/bar">Bar</router-link>
</div>
`
}
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const routes = [
{
path:'/',
name: 'First',
component: First,
default: First
},
{
path:'/foo',
name: 'Foo',
component: Foo
},
{
path:'/bar',
name: 'Bar',
component: Bar
}
]
const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')
// main.js
<div id="app">
<!-- route outlet -->
<!-- component matched by the route will render here -->
<router-view></router-view>
</div>
Some of the components needs to be hidden for particular Routes. I was able to achieve that using watcher for route change found from this SO question - Vuejs: Event on route change. I don't want to show header and sidebar in customizePage ( route - /customize ). But there is a problem when I do a hard reload from that particular page. That doesn't execute the watch and hence the it fails. The solution I found was having it also in mounted(), so that it executes also on reload.
But having the same function in mounted and watcher looks weird. Is there a better way to do it ?
<template>
<div>
<TrialBanner v-if="$store.state.website.is_trial"/>
<div class="page-body-wrapper" :class="{ 'p-0' : isCustomizePage}">
<Sidebar :key="$store.state.user.is_admin" v-if="!isCustomizePage"/>
<div class="main-panel" :class="{ 'm-0 w-100' : isCustomizePage}">
<Header v-if="!isCustomizePage"/>
<div class="content-wrapper" :class="{ 'p-0' : isCustomizePage}">
<router-view :key="$store.state.websiteId"></router-view>
</div>
</div>
</div>
</div>
</template>
mounted() {
if(this.$route.path == '/customize') {
this.isCustomizePage = true;
} else {
this.isCustomizePage = false;
}
},
watch: {
$route (to, from){
if(this.$route.path == '/customize') {
this.isCustomizePage = true;
} else {
this.isCustomizePage = false;
}
}
}
Easy fix:
Use an immediate watcher
watch: {
$route: {
immediate: true,
handler(to, from) {
if(this.$route.path == '/customize') {
this.isCustomizePage = true;
} else {
this.isCustomizePage = false;
}
}
}
}
More complex but more extensible fix:
Use "layout" components.
Demo
General idea is to create "Layout" components, use the meta tag on routes to define the layouts for each route, and then use a dynamic component in App.vue to tell the app which layout to use.
App.vue
<template>
<div id="app">
<component :is="layout">
<router-view></router-view>
</component>
</div>
</template>
<script>
export default {
name: "App",
computed: {
layout() {
return this.$route.meta.layout || 'default-layout';
}
}
};
</script>
Default layout component
<template>
<div>
<TrialBanner v-if="$store.state.website.is_trial"/>
<div class="page-body-wrapper" >
<Sidebar :key="$store.state.user.is_admin" />
<div class="main-panel">
<Header />
<div class="content-wrapper">
<slot></slot>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DefaultLayout',
};
</script>
Sample customize page layout
<template>
<div>
<TrialBanner v-if="$store.state.website.is_trial"/>
<div class="page-body-wrapper" class="p-0">
<div class="main-panel" class="m-0 w-100">
<div class="content-wrapper" class="p-0">
<slot></slot>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CustomizeLayout',
};
</script>
Main.js: register layout components as global components
import DefaultLayout from '#/layouts/DefaultLayout.vue';
import CustomizeLayout from '#/layouts/CustomizeLayout.vue';
Vue.component('default-layout', DefaultLayout);
Vue.component('customize-layout', CustomizeLayout);
Router.js: routes define the layouts for each route
const routes = [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/customize',
name: 'customize',
component: CustomizeView,
meta: {
layout: 'customize-layout'
}
}
];
The <slot></slot> in each layout component is where the View will render. You can also have multiple named slots and named views if you want to render different components in areas per layout.
I'm building a menu app using Vue JS. I was told that only have to use 1 component if the styling stays the same. So that means i have to use dynamic data. each menu/submenu has 3 to 4 menu links. I was looking for a solution to send variables with data to a component and came up with 'props'. But i couldn't find a way to send props from different routes to the same component and check which route you're on to know which props to load into the html.
I already tried to load props into a template, and that works fine. But sending and replacing prop values in the same part of html is something i haven't figured out yet.
{ path: '/list', component: List, props: { url: 'www.google.com' } }
<template>
<div class="container">
<h1>Welkom</h1>
<div class="row">
<div class="col-md-6">
<router-link to='/weed'>Wiet</router-link>
</div>
<div class="col-md-6">
<router-link to='/stuff'>Stuff</router-link>
</div>
<div class="col-md-6">
<router-link to='/joint'>Joint</router-link>
</div>
<div class="col-md-6">
<router-link to='/edibles'>Edibles</router-link>
</div>
</div>
</div>
</template>
the <router-link> should dynamic depending on which route you are, so it can load in different menu items.
I want a menu that loads in different route links depending on which route you are.
I think you should use Programmatic Navigation and query params like
router.push({ path: 'register', query: { plan: 'private' } })
so for your application use
<div class="col-md-6"
#click="$router.push({ path: '/edibles',
query: { url: 'www.google.com', other: 'etc' }})"
>Edibles</div>
you can access these query params in the navigated component using $route.params.url in a template and this.$route.params.url in functions and computed and all other properties of vue component instance.
also check https://router.vuejs.org/guide/essentials/navigation.html for details
Edit as per comment
I think you should still use Programmatic Navigation with
router.push({ name: 'user', params: { userId } })
the params provided can be used as props to the component as shown below
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true }
]
})
just remember to set props: true in routes definition!
You can use params from your route as props in your component and use a watcher to react to route changes. Either have a state system (vuex) with which you can access specific data regarding the params or just plainly use the params.
Use: router.push({ path: '/user/${userId}' })
then
watch: {
$route(to, from) {
// react somehow
}
}
and/or
template: '<div>User {{ $route.params.id }}</div>'
I have a 'views' page that imports two components, one of which is a NavBar that will display a loading animation until the other component is fully loaded in.
The way I'm trying to accomplish this, is I am trying to define a 'loading' var in the view, pass that var into the NavBar AND releases components. IF I change the loading to false from the releases component that should propagate over to the NavBar (to stop the loading animation).
views/Release.vue
<template>
<div>
<NavBar v-bind:loading="this.loading"></NavBar>
<div id="vue-main">
<h1><b>Releases</b></h1>
<Releases v-bind:loading="this.loading"></Releases>
<Footer></Footer>
</div>
</div>
</template>
<script>
import Releases from "../components/Releases.vue";
import NavBar from "./components/NavBar.vue";
export default {
name: "releases",
data () {
return {
loading: 'loading'
}
},
components: {
NavBar,
Releases,
}
};
</script>
components/NavBar.vue
<template>
<div>
<div id="nav">
<a href='/link1'>Link 1</a>
<a href='/link2'>Link 2</a>
<a href='/link3'>Link 3</a>
<pulse-loader :loading="this.loading"></pulse-loader>
</div>
</div>
</template>
<script>
import PulseLoader from 'vue-spinner/src/PulseLoader.vue';
export default {
name: 'NavBar',
props: ['loading'],
components: {
PulseLoader
},
};
</script>
I have left out Releases.vue from this post for brevity, but no matter where I set
this.loading=false
It does not seem to propagate over to NavBar component.
What am I doing wrong here? Not sure If I need to use $emit for something like this?
No, you should NOT modify the prop loading from Releases.vue.
In Releases.vue when data loaded, call $emit:
this.loadReleases()
.then(() => {
// Your logic.
this.$emit('loaded', true);
});
In the view Release.vue
<template>
<div>
<NavBar :loading="loading"></NavBar>
<div id="vue-main">
<h1><b>Releases</b></h1>
<Releases #loaded="updateLoading"></Releases>
<Footer></Footer>
</div>
</div>
</template>
<script>
import Releases from "../components/Releases.vue";
import NavBar from "./components/NavBar.vue";
export default {
name: "releases",
data () {
return {
loading: true,
}
},
components: {
NavBar,
Releases,
},
methods: {
updateLoading(val) {
this.loading = !val; // loading = false;
},
},
};
</script>
Please, use : instead of v-bind, # instead of v-on for making the code clear. And it's no need to use this on the template.
Do not use this in your templates.
<NavBar v-bind:loading="loading"></NavBar>
<div id="vue-main">
<h1><b>Releases</b></h1>
<Releases v-bind:loading="loading"></Releases>
<Footer></Footer>
<pulse-loader :loading="loading"></pulse-loader>
In fact, eveything in the template refers to this component, and you can't refer to anything else directly from the template.
$emit is for sending data up to the parent, and the main use case for that is to tell the parent to update a property that then flows back down to the component. Your use case is updating children, and using v-bind is appropriate, as the NavBar owns the data.
Im trying to install Vue.js with the router and im running into some view issues. I have a router.js with child routes. I want to use this method for simple breadcrumbs and generate a clear overview so i know which route belongs where.
Opening each route works like a charm, everything shows up. When i open /apps I get a nice view from my Apps.vue that displays App overview</h1>. But now im opening /apps/app-one and then I see the Apps.vue and AppOne.vue template. How can I prevent that both templates are displayed?
The vue components looks like this:
Router.js
import Router from 'vue-router';
import AppsPage from './components/Apps.vue'
import AppOne from './components/AppOne.vue'
import AppTwo from './components/AppTwo.vue'
export default new Router({
// mode: 'history',
routes: [
{
path: '/apps',
component: AppsPage,
children: [
{
path: '/apps/app-one',
component: AppOne,
},
{
path: '/apps/app-two',
component: AppTwo,
},
]
},
]
});
Apps.vue
<template>
<div id="app-overview">
<h1>App overview</h1>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app_page'
}
</script>
App1.vue
<template>
<div>
<h1>App 1</h1>
</div>
</template>
<script>
export default {
name: 'app_one'
}
</script>
App2.vue
<template>
<div>
<h1>App 2</h1>
</div>
</template>
<script>
export default {
name: 'app_two'
}
</script>
Having your routes in a parent-child relationship means that the child component will be rendered inside the parent component (at the <router-view>). This is expected behavior.
If you do not want the parent component to be visible when the child route is active, then the routes should be siblings, not nested:
[
{
path: '/apps',
component: AppsPage,
},
{
path: '/apps/app-one',
component: AppOne,
},
{
path: '/apps/app-two',
component: AppTwo,
},
]
The structure of the routes reflects the way they are rendered on the page.
It's possible and pretty easy too.You can achieve this by followings:
<template>
<div>
<div v-show="isExactActive">
Parent component contents will be here
</div>
<router-view ref="rv"></router-view>
</div>
</template>
<script>
export default {
data() {
return {
isExactActive: true,
}
},
updated() {
this.isExactActive = typeof this.$refs.rv === 'undefined';
},
mounted() {
this.isExactActive = typeof this.$refs.rv === 'undefined';
}
}
</script>
Hope, this will be helpful.