I'm trying to make my project to have 2 kind of nagigation bar, I have read some documentation and searching for tutorial on youtube, i did not found anything related to my problem, i want to have 2 kind of nav-bar for each route without refreshing it on every page. I put my nav-bar on app file in the current project, but what i want to do is making another nested routed that displaying another nav-bar and have it own routes, can anyone help me with this problem? simple code example would be greatly appreciated.
You could use the $route.meta for controlling which navbar to display. This is an easy solution, but you always have to take care of the nav (or set a default, like in the snippet below):
const Foo = {
template: `
<div>This is Foo</div>
`
}
const Bar = {
template: `
<div>This is Bar</div>
`
}
const routes = [{
path: "/",
redirect: "/foo",
},
{
path: "/foo",
component: Foo,
meta: {
nav: "nav1",
},
},
{
path: "/bar",
component: Bar,
meta: {
nav: "nav2",
},
},
]
const router = new VueRouter({
routes,
})
new Vue({
el: "#app",
components: {
Foo,
Bar,
},
router,
computed: {
computedRoute() {
return this.$route.meta.nav
},
},
})
.link {
padding: 0 8px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<div v-if="computedRoute === 'nav1'">
<router-link to="/foo" class="link">
FOO 1
</router-link>
<router-link to="/bar" class="link">
BAR 1
</router-link>
</div>
<div v-else>
<router-link to="/foo" class="link">
FOO 2
</router-link>
<router-link to="/bar" class="link">
BAR 2
</router-link>
</div>
<br>
<hr>
<router-view />
</div>
Related
I have created a simple navar where I have 3 links. All links are declared at a ROOT level of the router object. I've added a simple styling targeting the <router-link-active> class where the active link is highlighted on the navbar. This all works fine, switching between links updates the URL, changes the <router-view> as well as applies correct style to the navbar link currently on.
The issue I'm having is that whenever I click on a 4th link which is also declared on the ROOT level of the router object, starting with the same path name as currently active link, the <router-link-active> class disasters. e.g.
{ path: "/link2", component: link2 },
{ path: "/link2/sibling", component: sibling },
My understanding is because the /links2/sibling starts with the same name as /link2, the navbar item that navigates to /link2 should still be have the <router-link-active> class, even when the /link2/sibling is currently active URL.
Codesandbox
App.vue
<template>
<div>
<ul class="flex gap-x-5">
<router-link to="/">
<li>Link 1</li>
</router-link>
<router-link to="/link2">
<li>Link 2</li>
</router-link>
<router-link to="/link3">
<li>Link 3</li>
</router-link>
</ul>
</div>
<router-view />
</template>
<script>
export default {
name: "App",
};
</script>
<style>
a:hover,
a:active,
a.router-link-active {
color: #f1a80a;
border-color: #f1a80a;
background-color: #1a037e;
}
</style>
main.js
import App from "./App.vue";
import router from "./router.js";
const app = createApp(App);
app.use(router);
app.mount("#app");
router.js
import { createRouter, createWebHistory } from "vue-router";
import link1 from "./components/link1.vue";
import link2 from "./components/link2.vue";
import sibling from "./components/sibling.vue";
import link3 from "./components/link3.vue";
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/", component: link1 },
{ path: "/link2", component: link2 },
{ path: "/link2/sibling", component: sibling },
{ path: "/link3", component: link3 }
]
});
export default router;
link1.vue
<template>
<div>You are inside Link1</div>
</template>
link2.vue
<template>
<div>
<router-link to="/link2/sibling">
You are inside Link 2 (CLICK ME)
</router-link>
</div>
</template>
link3.vue
<template>
<div>You are inside Link 3</div>
</template>
sibling.vue
<template>
<div>You are inside Link2 sibling</div>
</template>
I think that is the natural behavior that we could expect from routing. when you click on You are inside Link 2 (CLICK ME) inside link2.vue component, the vue-router loads sibling.vue in the router-view part in your App.vue. So there is no You are inside Link 2 (CLICK ME) link in that view to see the router-link-active styles. If you want to see that styles, you must keep your link in the view and don't allow vue-router to disappear that.
For achieving such a goal you can use Nested Routes in vue-router like this. First change your router.js file to something like this:
import { createRouter, createWebHistory } from "vue-router";
import link1 from "./components/link1.vue";
import link2 from "./components/link2.vue";
import sibling from "./components/sibling.vue";
import link3 from "./components/link3.vue";
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/", component: link1 },
{
path: "/link2",
component: link2,
children: [
{
path: 'sibling',
component: sibling
},
]
},
// { path: "/link2/sibling", component: sibling
//
// },
{ path: "/link3", component: link3 }
]
});
export default router;
And then add a <router-view> to your link2.vue file like this:
<template>
<div>
<router-link to="/link2/sibling">
You are inside Link 2 (CLICK ME)
</router-link>
<router-view></router-view>
</div>
</template>
I know about the scrollBehavior function that Vue has, but I am unsure of how to use it with my code.
I have an Index page, where sections are made of imported vue components.
For example
<template>
<div class="Container">
<About />
<Services />
<Contact />
</div>
</template>
My Navbar has links to all these components.
<template>
<nav>
<img class="nav__logo" :src="navLogo" height="40px" width="auto">
<ul class="nav__row" ref="nav">
<div class="nav__row__dropdown-toggle-btn" #click="toggleNav">
<img :src="navMenuIcon" height="40px" width="auto">
</div>
<li class="nav__list-item" v-for="(link, index) in navLinks" :key="index"
#mouseover="hover = true"
#mouseleave="hover = false"
:class=" { active: hover } ">
<router-link :to="link.path">
{{ link.text }}
</router-link>
</li>
</ul>
</nav>
</template>
which it gets from my App.vue script
<script>
import Navbar from '#/components/Navbar'
import Footer from '#/components/Footer'
export default {
components: {
Navbar,
Footer
},
data: () => ({
navLinks: [
{
text: 'Home',
path: '/'
},
{
text: 'About',
path: '/about'
},
{
text: 'Contact',
path: '/contact'
},
{
text: 'Services',
path: '/services'
}
]
})
}
</script>
but if I click on "About" it will just take me to a seperate page for the "About" component.
When I click on "About" I want the page to scroll down to the imported About component that is nested on my Index page.
How can I accomplish this? Is it something I need to change in the path?
The idea of router that you trying to implement is actually treating About components as a page, and when clicking at it, it will go to that page (and change URL)
So, in order to implement what you want, I actually suggest using pure javascript
First, give the About component and id ( or ref if you prefer Vue ref)
<template>
<div class="Container">
<About id="about"/>
<Services />
<Contact />
</div>
</template>
Then bind a method to click event of nav
<li class="nav__list-item" v-for="(link, index) in navLinks" :key="index"
#mouseover="hover = true"
#mouseleave="hover = false"
:class=" { active: hover } ">
#click=nav(link)
</li>
data: () => ({
navLinks: [
{
text: 'About',
id: 'about'
}
]
}),
methods:{
nav(link) {
const position = document.getElementById(link.id).offsetTop;
// smooth scroll
window.scrollTo({ top: position, behavior: "smooth" });
}
}
If you prefer Vue ref, can change all id to ref. And use this.$refs[link.id].$el.offsetTop instead;
i found this video so useful,
my goal was able to scroll between component at the same page.
but still you can apply anywhere, i think
https://laracasts.com/series/practical-vue-components/episodes/1
I need some help displaying a selected an item on the page you are on. I have three component Home, Product, and Index. The Index component is Global component which have a list of items with a route-link attached to them to got to the Product Page (Component) and display the item that was click. I am passing the item in the route-link as params to be able to access the item on the Product page. I am using the Index Component on the Home component to display all item. And when I click an item from the Index component from the Home page, it go to the Product page and display that item. That part is working fine, but when I am on the Product page and I click an item from the Index component, it is not displaying the clicked item in the Product page. I really need some help how to solve this problem.
Main.js code
import Vue from 'vue'
import App from './App'
import router from './router/routes'
import Index from './components/Index'
Vue.component('list-items', Index)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: {App},
template: '<App/>'
})
Route.js File
import Vue from 'vue'
import Router from 'vue-router'
import Home from '../pages/Home'
import Product from '../pages/Product'
Vue.use(Router)
export default new Router({
routes: [
{
path: '' || '/',
name: 'Home',
component: Home
},
{
path: '/product',
name: 'Product',
component: Product,
props: true
}
],
mode: 'history'
})
Home.vue component
<template>
<div class="container-fluid p-0">
<list-items />
</div>
</template>
<script>
export default {
name: 'Home',
data: () => ({
})
}
Product.vue component
<template>
<div class="container-fluid p-0">
<div class="container-fluid py-5">
<div class="row">
<div class="col-md-7">
<div class="col-md-12">
<img v-bind:src="item.url" alt="" class="img-fluid">
</div>
</div>
<div class="col-md-4 margin-padding-right">
<div class="col-md-12">
<h3>{{$route.params.product.name}}</h3>
<h5 class="price">${{item.price}}</h5>
</div>
</div>
</div>
<br>
<div class="container-fluid">
<div class="col-md-12 related-item">
<h5>Related Items</h5>
</div>
<list-items />
</div>
</div>
</div>
<script>
export default {
name: 'Product',
props: ['product'],
data: () => ({
quantity: 1,
item: {
name: '',
url: ''
}
}),
mounted () {
if (this.product) {
this.item.name = this.product.name
this.item.url = this.product.image
}
},
watch: {
change: function (newValue, oldValue) {
this.item.name = newValue.product.name
}
}
}
</script>
<style lang="scss" scoped>
</style>
This is an image of what I am trying to achieve.
I'm running into an issue when trying to redirect from one component to another. It appears that it's not routing to the URL thats specified in my router to the desired component and is staying on my home page instead. I can't figure out where the error is occuring.
I'm using the Vue CLI version 3.
Below is my index.js, Home.vue and Model.vue
Then under that is images of the Home.vue then it shows what happens when I try to redirect to the link in my href tag.
You can see that it's not going to the other component and it's staying on my home page.
Any ideas on whats causing the issue here? Thanks!
/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Homefrom '#/components/Home'
import Model from '#/components/Model'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/model/:model_tag_name',
name: 'Model',
component: Model
// props: true
}
]
})
/components/Home.vue
<template>
<div class="hello container-fluid">
<h1>{{msg}}</h1>
<div class="row">
<div class="col-4 text-left">
<ol>
<li v-for="tag in tags" v-bind:key="tag.model_tag_name">
<a :href="'/model/'+ tag.model_tag_name"> {{tag.model_tag_name}}</a>
</li>
</ol>
</div>
<div class="col-8">
<p>Data</p>
</div>
</div>
</div>
</template>
<script>
var axios = require('axios');
export default {
name: 'Home',
data () {
return {
msg: 'Welcome to Your Vue.js App',
tags: []
}
},
mounted: function() {
var url = 'http://10.0.0.5:5000';
console.log(url)
axios.get(url + '/')
.then((response) => {
console.log(response.data);
this.tags = [{"model_tag_name": response.data}];
})
.catch(function(error) {
console.log(error);
});
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
a {
color: #42b983;
}
</style>
/components/Model.vue
<template>
<div class="container-fluid">
<h1> Model </h1>
</div>
</template>
<script>
var axios = require('axios');
export default {
name: 'Model',
data () {
return {
model_tag_name: this.$route.params.model_tag_name
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
a {
color: #42b983;
}
</style>
http://localhost:8080/
Then this is what happenes when I click the href link on the home page. It's redirecting back to the home page even though the URL matches the routerview for Model.vue
Pleas update this code in /components/Home.vue
<li v-for="tag in tags" v-bind:key="tag.model_tag_name">
<router-link :to="{ name: 'Model', params: { model_tag_name: tag.model_tag_name}}">
{{tag.model_tag_name}}</router-link>
</li>
I have created this Vue.js router example to wrap my mind around how routing works. I intentionally load it everything from CDN so that whoever looks at this, gets to begin learning immediately instead of having to learn how to import dependencies, etc.
My question is, where do I place the methods that are going to fetch data from a JSON API based on the route parameter of that route? In the App, or in the Component?
Forgive if my question seems naive. I think I'm almost there.
Run Code Snippet then click Full Page to better view.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Simple Vue.js Router Example</title>
<!-- VUE JS v2.6.1 -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- VUE ROUTER JS v3.1.3 -->
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<!-- BOOTSTRAP CSS v4.3.1 -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<!-- GOOGLE FONT CSS - Roboto Mono -->
<link href="https://fonts.googleapis.com/css?family=Roboto+Mono:100,300,400,500,700&display=swap" rel="stylesheet">
<!-- GOOGLE FONT CSS - Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style type="text/css">
body {
font-family: 'Roboto Mono', monospace;
font-weight: 400;
font-size: 1rem;
background-color: #e0e0e0;
}
.active {
color: #f44336;
}
</style>
</head>
<body>
<!-- VUE APP - PARENT CONTAINER -->
<div id="app" class="container">
<!-- HEADER CONTAINER -->
<header>
<hr>
<h1>Header</h1>
<p>Static header text</p>
<ul>
<li>
<router-link to="/">/ </router-link>
</li>
<li>
<router-link to="/users">/users</router-link>
</li>
<li>
<router-link to="/users/123">/users/123</router-link>
</li>
<li>
<router-link to="/posts">/posts</router-link>
</li>
<li>
<router-link to="/posts/456">/posts/456</router-link>
</li>
<li>
<router-link to="/unsaved-changes">/unsaved-changes</router-link>
</li>
<li>
<router-link to="/unknown-route/789">/unknown-route/789</router-link>
<br>
<small>*forwards to route /404</small>
</li>
</ul>
</header>
<!-- MAIN CONTAINER -->
<main>
<hr>
<h1>Main</h1>
<p>Static main text</p>
<router-view name="routerView0"></router-view>
<router-view name="routerView1"></router-view>
<router-view name="routerView2"></router-view>
<router-view name="routerView3"></router-view>
<router-view name="routerView4"></router-view>
<router-view name="routerView5"></router-view>
<router-view name="routerView6"></router-view>
</main>
<!-- FOOTER CONTAINER -->
<footer>
<hr>
<h1>Footer</h1>
<p>Static footer text</p>
</footer>
</div>
<!-- JAVA SCRIPT -->
<script type="text/javascript">
// DISABLE
Vue.config.productionTip = false;
// DISABLE
Vue.config.devtools = false;
// COMPONENT 0
const Component0 = {
template:
`
<div style="background-color: #bcaaa4;">
<strong>Component 0</strong>
<br>
success: route /
<br>
result: component rendered.
</div>
`
}
// COMPONENT 1
const Component1 = {
template:
`
<div style="background-color: #80deea;">
<strong>Component 1</strong>
<br>
success: route /users
<br>
result: component rendered.
</div>
`
}
// COMPONENT 2
const Component2 = {
template:
`
<div style="background-color: #80deea;">
<strong>Component 2</strong>
<br>
success: route /users/{{ $route.params.id }}
<br>
result: component rendered.
</div>
`
}
// COMPONENT 3
const Component3 = {
template:
`
<div style="background-color: #b39ddb;">
<strong>Component 3</strong>
<br>
success: route /posts
<br>
result: component rendered.
</div>
`
}
// COMPONENT 4
const Component4 = {
template:
`
<div style="background-color: #b39ddb;">
<strong>Component 4</strong>
<br>
success: route /posts/{{ $route.params.id }}
<br>
result: component rendered.
</div>
`
}
// COMPONENT 5
const Component5 = {
template:
`
<div style="background-color: #ffe082;">
<strong>Component 5</strong>
<br>
success: route /unsaved-changes
<br>
result: component rendered.
<br>
<small><strong>*If you leave this route,<br> all text typed in below will be lost.</strong></small>
<br>
<input type="text">
</div>
`,
// IN COMPOMENT ONLY...
beforeRouteLeave (to, from, next) {
const answer = window.confirm('Are you sure you want to leave this route? There are unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
}
// COMPONENT 6
const Component6 = {
template:
`
<div style="background-color: #ef9a9a;">
<strong>Component 6</strong>
<br>
error: unknown route.
<br>
action: forwarded to route /404.
<br>
result: component rendered.
</div>
`
}
// IN THIS ROUTE I WILL RENDER THESE COMPONENTS..
const router = new VueRouter({
mode: 'hash',
linkExactActiveClass: "active",
routes: [
// ROUTE 0
{ path: '/',
// COMPONENT(S) TO RENDER IN ORDER
components: {
// ONE OR MORE...
routerView0: Component0,
}
}, // END ROUTE 0
// ROUTE 1
{ path: '/users',
// COMPONENT(S) TO RENDER
components: {
// ONE OR MORE...
routerView1: Component1,
}
}, // END ROUTE 1
// ROUTE 1.1
{ path: '/users/:id',
// COMPONENT(S) TO RENDER
components: {
// ONE OR MORE...
routerView2: Component2,
},
// REPORT WHEN THIS ROUTE IS VISITED
beforeEnter: (to, from, next) => {
// ...
console.warn('ROUTE CHANGE')
console.log('ROUTE', 'FROM:', from.path, 'TO:', to.path);
next();
}
}, // END ROUTE 1.1
// ROUTE 2
{
path: '/posts',
components: {
// ONE OR MORE...
routerView3: Component3,
}
}, // END ROUTE 2
// ROUTE 2.1
{
path: '/posts/:id',
components: {
// ONE OR MORE...
routerView4: Component4,
}, // END ROUTE 2.1
// REPORT WHEN THIS ROUTE IS VISITED
beforeEnter: (to, from, next) => {
// ...
console.warn('ROUTE CHANGE')
console.log('ROUTE', 'FROM:', from.path, 'TO:', to.path);
next();
}
},
// ROUTE UNSAVED CHANGES
{
path: '/unsaved-changes',
components: {
// ONE OR MORE...
routerView5: Component5,
}
}, // END ROUTE UNSAVED CHANGES
// REDIRECT!
{
path: '*', redirect: '/404',
// TRAP ANY UNDEFINED ROUTE AND...
// FORWARD IT TO /404 ROUTE
},
// ROUTE UNDEFINED - CUSTOM PAGE
{
path: '/404',
components: {
// ONE OR MORE...
routerView6: Component6,
}
}, // END ROUTE UNDEFINED
]
});
// WATCH EVERY ROUTE THAT IS VISITED
/*
router.beforeEach((to, from, next) => {
// ...
console.info('Global Route Watcher')
console.log('ROUTE', 'FROM:', from.path, 'TO:', to.path);
next();
});
*/
const App = new Vue({
el: '#app',
router,
data: {
},
})
</script>
</body>
</html>
I personally do the fetch inside the components, you can fetch the data on the created or beforeCreate hook if you want the component to render before fetching the data and add a loading animation, alternatively you could use the beforeRouteEnter or beforeRouteUpdate hooks on the component.
My logic behind this is the following, if you fetch the data from the component they would be easier to test by just passing them a new route param and you avoid doing some complex logic on the App component because it wont have to track which route is selected and act accordingly.
take a look on solid and Single responsibility principle