I have a Vue component named ComponentSearch, It is used to search.
<template>
<div class="cs">
<div>
<el-input
placeholder="search"
prefix-icon="el-icon-search"
v-model="keyword"
#change="search">
</el-input>
</div>
</div>
</template>
<script>
export default {
name:'ComponentSearch',
data() {
return {
keyword: ''
}
},
methods: {
search() {
if (this.$route.path !== '/component/search') {
// this means jumping over from other pages via the search component, not within the search page
this.$router.push({
path: '/component/search',
query: {
keyword: this.keyword
}
})
} else {
// this means that searching through the search page
this.$router.replace({
query: {
keyword: this.keyword
}
})
}
}
}
}
</script>
Here is my search page.
<template>
<div id="search">
<-- this is the search component -->
<ComponentSearch></ComponentSearch>
<div id="container">
<-- display the data from this.list -->
</div>
</div>
</template>
<script>
import ComponentSearch from '#/components/ComponentSearch.vue'
export default {
components: {
ComponentSearch
},
data() {
return {
list: []
}
},
methods: {
/**
* request data from the backend based on keywords
* #param {String} keyword Search keywords
* #param {Number} index Current page number, default 1
* #param {Number} size Size per page, default 10
*/
getComponentListByKeyword(keyword, index = 1, size = 10) {
}
},
// access to http://localhost:3000/component/search from other routes
// from.$route.path !== '/component/search'
beforeRouteEnter(to, from, next) {
console.log('before route enter...');
console.log(to);
console.log(from);
let keyword = to.query.keyword
if (keyword) {
// access search page from other pages via the search component, keyword is assigned
next(vm => {
vm.getComponentListByKeyword(keyword)
})
} else {
// enter the search page http://localhost:8080/component/search directly from the browser by typing the url
next()
}
},
// search page routing update
beforeRouteUpdate(to, from) {
console.log('before route update...');
console.log(to);
console.log(from);
this.getComponentListByKeyword(to.query.keyword)
}
}
</script>
Here's how I do it.
I entered the search parameter x from the page http://localhost:8080/component through the search component and it worked fine and jumped to the search page.
http://localhost:8080/component is any page different from the search page, its url may be
http://localhost:8080/a,
http://localhost:8080/b,
or http://localhost:8080/c,
but It can never be http://localhost:8080/component/search.
These pages all contain a search component, ComponentSearch, and their structure is probably something like this
<template>
<div id="uniqueID">
<ComponentSearch></ComponentSearch>
<div>
<-- other html tags-->
<div>
</div>
</template>
<script>
import ComponentSearch from "#/components/ComponentSearch.vue"
export default {
components: {
ComponentSearch
},
data() {
return { }
}
</script>
The browser url now shows http://localhost:8080/component/search?keyword=x
Here are some console output messages.
//before route enter...
// from
{
fullPath: "/component"
hash: ""
matched: [{…}]
meta: {}
name: "Component"
params: {}
path: "/component"
query: {}
}
// to
{
fullPath: "/component/search?keyword=x"
hash: ""
matched: [{…}]
meta: {}
name: "ComponentSearch"
params: {}
path: "/component/search"
query: {keyword: 'x'}
}
Then I entered the keyword y through the search component on the search page and it worked fine.
But now the browser url still shows http://localhost:8080/component/search?keyword=x
While the console outputs
//before route update...
// from
{
fullPath: "/component/search?keyword=x"
hash: ""
matched: [{…}]
meta: {}
name: "ComponentSearch"
params: {}
path: "/component/search"
query: {keyword: 'x'}
}
// to
{
fullPath: "/component/search?keyword=y"
hash: ""
matched: [{…}]
meta: {}
name: "ComponentSearch"
params: {}
path: "/component/search"
query: {keyword: 'y'}
}
Question, why does the browser url not replace the value of the parameter keyword x with y.
When I type http://localhost:8080/component/search directly into the browser and then query the data through the search component, it works fine but the browser url still shows http://localhost:8080/component/search, without splicing in the ?keyword=
Console output when entering URL http://localhost:8080/component/search directly from the browser:
//before route enter...
// from
{
fullPath: "/"
hash: ""
matched: [{…}]
meta: {}
name: null
params: {}
path: "/"
query: {}
}
// to
{
fullPath: "/component/search"
hash: ""
matched: [{…}]
meta: {}
name: "ComponentSearch"
params: {}
path: "/component/search"
query: {}
}
After typing the URL directly and querying with the keyword z, the console output:
//before route update...
// from
{
fullPath: "/component/search"
hash: ""
matched: [{…}]
meta: {}
name: "ComponentSearch"
params: {}
path: "/component/search"
query: {}
}
// to
{
fullPath: "/component/search?keyword=z"
hash: ""
matched: [{…}]
meta: {}
name: "ComponentSearch"
params: {}
path: "/component/search"
query: {keyword: 'z'}
}
The browser URL still shows http://localhost:8080/component/search, which does not splice ?keyword=z to the browser URL.
But they are able to query the data normally, it's just that the URL on the browser never changes.
Also, I have set the corresponding page in the routing file.
I have also tried using $route.params.xxx, which has the same error as $route.query.xxx.
{
// path: '/component/search/:keyword',
path: '/component/search',
name: 'ComponentSearch',
component: () => import('../views/component/search.vue'),
},
Related
I have created a MENU where I link via <router-link> but certain links are linked to the same page using (anchors).
When I'm on the Work page and I click on the #services section, which is on the Bio page, the section is displayed correctly, but if I want to go to the services section on the Bio page, the URL just changes, but it won't go to the right section for me.
noubtest.com
NAVIGATION
<router-link v-show="!mobile" class="link bio" :to="{ name: 'Home' }">Bio</router-link>
<router-link v-show="!mobile" class="link link2" :to="{ name: 'Services' }">Services</router-link>
<router-link v-show="!mobile" class="link link2" :to="{ name: 'SelectedWork' }">Work</router-link>
ROUTER
{
path: "/home",
name: "Home",
component: Home,
meta: {
title: "Bio",
requiresAuth: false,
},
},
{
path: "/home#fifthPage",
name: "Services",
component: Home,
meta: {
title: "Services",
requiresAuth: false,
},
},
const router = new VueRouter({
mode: "history",
routes,
scrollBehavior() {
return { x: 0, y: 0 };
},
});
router.beforeEach((to, from, next) => {
document.title = `${to.meta.title} | YounesFilm`;
next();
});
router.beforeEach(async (to, from, next) => {
let user = firebase.auth().currentUser;
let admin = null;
if (user) {
let token = await user.getIdTokenResult();
admin = token.claims.admin;
}
if (to.matched.some((res) => res.meta.requiresAuth)) {
if (user) {
if (to.matched.some((res) => res.meta.requiresAdmin)) {
if (admin) {
return next();
}
return next({ name: "Home" });
}
return next();
}
return next({ name: "Home" });
}
return next();
});
export default router;
How can I click through the page between sections?
You must switch your VueRouter from hash mode to history mode of routing - then hashtags will work but in a different way.
Your routes should not have a hash symbol # inside their path - instead, you should provide it under the hash attribute of the route link:
<router-link :to="{ name: pathName, hash: '#text' }">
Jump to content
</router-link>
But this alone is not enough. You also need to alter the scrollBehavior of the VueRouter:
import { routes } from './routes.js';
const router = new VueRouter({
routes,
scrollBehavior(to, from, savedPosition)
{
if (savedPosition)
{
return savedPosition;
}
if (to.hash)
{
return { selector: to.hash }; // <==== the important part
}
return { x: 0, y: 0 };
}
});
With a few research, I found two things that could help you.
First, this error is known and discussed on github vue-router issues page.
Second, I found that Workaround on npmjs.com, and you could probably give it a try.
EDIT
I found another solution to a similar problem here.
And from that page, I found a scrollBehavior example like this:
scrollBehavior: function (to) {
if (to.hash) {
return {
selector: to.hash
}
}
}
And if it still doesn't work, you could try to use
:to="{ name: 'Home', hash: 'fifthPage'}".
I want to navigate to a specific tab in a page, with
this.$router.push({
name: "AdminNotifications",
params: { tab: "requests" },
})
so inside the page i can get the param and set the tab:
mounted() {
const routeTab = this.$route.params.tab;
if (routeTab) this.tab = routeTab;
}
It works if the current page is not AdminNotifications.
But else, there is an error:
NavigationDuplicated: Avoided redundant navigation to current
So... is there a way to just set the tab props, without navigate?
thanks
You can't navigate to a route if you're already there. But, since you're already there, you can just set this.tab to the desired value:
if (this.$route.name === 'AdminNotifications') {
this.tab = 'requests';
} else {
this.$router.push({
name: "AdminNotifications",
params: { tab: "requests" },
})
}
If the component in charge of navigating is not the same as the one containing tab, you could push the tab param to the $route:
if (this.$route.name === 'AdminNotifications') {
this.$router.replace({
params: { tab: "requests" }
});
} else {
this.$router.push({
name: "AdminNotifications",
params: { tab: "requests" },
})
}
And in the page component, replace the "watcher" in mounted with a proper watch, which sets tab to any truthy value of $route.params.tab, dynamically:
watch: {
'$route.params.tab': {
handler(val) {
if (val) {
this.tab = val;
}
},
immediate: true
}
}
If i understood your question correctly you can just do this.$route.params.tab = "any value" like any other variable. this.$route.params.tab is just a variable like all the others.
Here is how I was handling this with the vue-router.
Add one parent component which will contain tabs and a tab content components.
Your structure can looke like this:
tabs/Tab1.vue
tabs/Tab2.vue
tabs/Tab2.vue
Tab.vue
In Tabs.vue paste code below. The component should contain in the place where you want to display the content of your tabs and router-links to link a specific tab.
Tab.vue
<template>
<div class="tabs">
<router-link to="/tab1">Tab 1</router-link>
<router-link to="/tab2">Tab 2</router-link>
<router-link to="/tab3">Tab 3</router-link>
<router-view />
</div>
</template>
<script>
export default {
name: "tabs",
components: {},
};
</script>
Then fill tabs content components.
In your router.js register your tab routes as shown below.
import Tabs from "./Tabs";
import Tab1 from "./tabs/Tab1";
import Tab2 from "./tabs/Tab2";
import Tab3 from "./tabs/Tab3";
{
path: "/",
redirect: "/tab1",
component: Tabs,
children: [
{
path: "/tab1",
name: "tab1",
component: Tab1
},
{
path: "/tab2",
name: "tab2",
component: Tab2
},
{
path: "/tab3",
name: "tab3",
component: Tab3
}
]
}
Now you should be able to navigate a specific tab by router link.
In the file where you define the routes, you need to define the props for each route, something like this:
const routes = [
{
path: "admin-notifications",
name: "AdminNotifications",
component: AdminNotificationsView,
props: r => ({
tab: r.params.tab
})
}
]
And then define the prop tab in AdminNotificationsView assuming that's the component you use to render the view.
I have a router guard beforeEach route to watch if there's user authenticated:
import Vue from "vue";
import VueRouter from "vue-router"
import Login from "../views/Login.vue"
import Home from "../components/Home.vue"
import Register from "../views/Register.vue"
import Dashboard from "../views/Dashboard.vue"
import Pricing from "../components/Pricing.vue"
import Invoices from "../components/Invoices.vue"
import { FirebaseAuth } from "../firebase/firebase"
Vue.use(VueRouter);
const routes = [
{
path: "*",
redirect: "/login",
},
{
path: "/dashboard",
name: "dashboard",
component: Dashboard,
children: [
{
path: "home",
name: "home",
component: Home,
},
{
path: "pricing",
name: "pricing",
component: Pricing,
},
{
path: "invoices",
name: "invoices",
component: Invoices,
}
],
meta: {
auth: true,
},
redirect: "home"
},
{
path: "/login",
name: "login",
component: Login,
},
{
path: "/register",
name: "register",
component: Register,
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
router.beforeEach((to, from, next)=>{
let user = FirebaseAuth.currentUser;
let auth = to.matched.some(record => record.meta.auth);
if (auth && !user) {
next('/login');
} else if (!auth && user) {
next('/dashboard/home');
} else{
next();
}
});
export default router;
When I perform logouts and logins there's an error about redundant navigation, however, I just assumed that it's ok if I just catch this.$router.push('/dashboard/home').catch(err => err); and move on without the console.log err. But creating an alert on component created() I've noticed that the thing is just more serious than what I thought, the component that shows the alert on created() it's showing it three times, and as I have a fetch for restore items on created(), that function is being triggered three times which is obviously not the performance wanted.
async created() {
alert("created")
this.credits = await fetchCredits(this.$firestore, this.$auth.currentUser);
let role = await getCustomClaimRole(this.$auth.currentUser);
this.subscription = role
? role.charAt(0).toUpperCase() + role.slice(1) + " " + "plan"
: "You haven't subscribed yet";
this.isLoading();
},
inside fetchCredits() is the console.log triggering three times
export const fetchCredits = async function (firestore, currentUser) {
// firestore collection of customers
const db = firestore.collection("customers");
/**
* Let's fetch the credits from the user:
*/
const credits = (await db.doc(currentUser.uid).get()).data();
if (credits !== "undefined") {
console.log(credits);
return credits.credits
} else {
return 0;
}
}
I think the problem is with the navigation guard, however, correct me if I'm wrong, but how to solve this?
I think that it has something to do with your router path:
{
path: "*",
redirect: "/login",
},
I have used Vue Router several times, but since I hadn't used wildcards before, I built a simplified Vue 2 CLI test application.
My router:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import Home from '#/components/stackoverflow/router-wildcard-match/Home'
import RouteOne from '#/components/stackoverflow/router-wildcard-match/RouteOne'
import RouteTwo from '#/components/stackoverflow/router-wildcard-match/RouteTwo'
import WildCard from '#/components/stackoverflow/router-wildcard-match/WildCard'
const routes = [
{
path: '/*',
name: 'wildcard',
component: WildCard
},
{
path: '/home',
name: 'home',
component: Home,
},
{
path: '/routeone',
name: 'routeOne',
component: RouteOne,
},
{
path: '/routetwo',
name: 'routeTwo',
component: RouteTwo,
},
]
export default new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
and my navbar component that routes programmatically:
<template>
<div class="navbar-sandbox">
<nav class="navbar navbar-expand-md navbar-light bg-light">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#" #click.prevent="navigate('home')">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" #click.prevent="navigate('routeone')">RouteOne</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" #click.prevent="navigate('routetwo')">RouteTwo</a>
</li>
</ul>
</nav>
</div>
</template>
<script>
export default {
data() {
return {
//currentRoute: 'home',
currentPath: 'home'
}
},
methods: {
// NOTE: Using route names work regardless of having wildcard path
// navigate(route) {
// if (route !== this.currentRoute) {
// this.currentRoute = route;
// this.$router.push({ name: route });
// }
// },
navigate(path) {
if (path !== this.currentPath) {
this.currentPath = path;
this.$router.push({ path: path });
}
}
}
}
</script>
As you can see in my code comments, when I programmatically route via the route names, it works even with having a wildcard path, but when I route via the actual route path, the routes are all intercepted by the wildcard.
My wildcard path is a bit different that yours, /* vs *.
I've build a vue.js web app for an insurance brokerage where every agent has their own website that is generated from their profiles.
This is what the link looks like in my vue-router index file"
{
path: '/agents/:id',
name: 'AgentSite',
component: AgentSite
},
Everything works great EXCEPT that the urls are getting too long to fit on some business cards. I would like to change the URLs to be like this:
{
path: '/:id',
name: 'AgentSite',
component: AgentSite
},
However, then every other bit of dynamic content in the app loads our agent website template (AgentSite). Quotes, Clients, Policies... they won't load properly.
Is there a way to remove the "/agents" from the URLs without messing up the rest of our application? I could shorten it to "/a/:id but that ends up being more confusing than it's worth.
Thanks!
EDIT: a couple of people have mentioned solutions that work when the agent id is a number. That's a great idea except that we have built agent "slugs" to use instead.
On the agent website layout:
created() {
console.log(this.$route.params.id);
this.$store.dispatch("getAgentFromSlug", this.$route.params.id);
}
and in the store:
getAgentFromSlug({commit}, payload){
const db = firebase.database();
db.ref("users/").orderByChild("slug").equalTo(payload).once("value",
(snap) => {
console.log(snap.val());
var info = snap.val();
commit("setAgentSiteInfo", info[Object.keys(info)[0]])
})
}
So, our route Id is really a slug.
Considering ids are numbers, you could use:
{
path: '/:id(\\d+)',
name: 'AgentSite',
component: AgentSite
},
Which only matches if id is made only of numbers.
Update: A couple of people have mentioned solutions that work when the agent id is a number. That's a great idea except that we have built agent "slugs" to use instead.
If the names can conflict with existing routes, declare the agent route last.
From the Matching Priority docs (emphasis mine):
Matching Priority
Sometimes the same URL may be matched by multiple routes. In such a
case the matching priority is determined by the order of route
definition: the earlier a route is defined, the higher priority it
gets.
In other words, declare like:
routes: [
{
path: '/',
component: HomePage
},
{
path: '/quotes',
component: Quotes
},
{
path: '/clients',
component: Clients
},
{
path: '/:id',
component: AgentSite,
props: true
}
]
See CodeSandbox demo Here.
Handling 404 pages
Would I then declare the 404 page route above or below the "AgentSite" in your example? { path: "*", component: PageNotFound }
The AgentSite route would match any URL not matched previously, so you'll have to handle the 404s inside the AgentSite component.
First, declare the 404 route after the AgentSite:
routes: [
// ... (other routes)
{
path: "/:id",
component: AgentSite,
props: true
},
{
path: ":path",
name: "404",
component: p404,
props: true
}
]
Then, inside AgentSite, get the agent :id, check if it is a known agent and, if not, redirect to the 404 route by name (otherwise it would match agent again).
export default {
props: ["id"],
data() {
return {
availableAgents: ["scully", "bond", "nikita"]
};
},
created() {
let isExistingAgent = this.availableAgents.includes(this.id);
if (!isExistingAgent) {
this.$router.push({
name: "404",
params: { path: this.$route.fullPath.substring(1) }
});
}
}
};
The CodeSandbox demo Here already contains this handling.
You can use regex matching if you :id has a specific format (example from vue-router repository).
For example, if your :id is a number:
const routes = [
{ path: '/:id(\\d+)', component: Foo },
{ path: '/bar', component: Bar }
]
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const routes = [
{ path: '/:id(\\d+)', component: Foo },
{ path: '/bar', component: Bar }
]
const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')
.router-link-active {
color: red;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<h1>Hello App!</h1>
<p>
<router-link to="/321321">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
</div>
Normally, in an app, I would put my partials in a template file.
Something like:
<app>
<nav></nav>
<sidebar></sidebar>
<router-view></router-view>
<footer></footer>
</app>
Depending on the route (login), I want to use a different template.
<app>
<login></login>
</app>
I was thinking I could create two components: say landing-page and Backend.
routes: [
{
path: '/',
name: 'Login',
component: Login
},
{
path: '/dashboard',
name: 'content',
component: Backend
}
]
Backend could look like I want it to:
<backend>
<nav></nav>
<sidebar></sidebar>
<router-view></router-view>
<footer></footer>
</backend>
However, how would i specify that then the route is \dashboard, I should render the dashboard component?
File router/index.js
export default new Router({
routes: [
{
path: '/',
component: Page,
children: [
{
path: '',
name: 'Dashboard',
component: Dashboard,
auth: true
},
{
path: 'users',
name: 'Users',
component: Users,
auth: true
}
]
},
{
path: '/login',
name: 'Login',
component: Login
}
]
})
App.vue
<template>
<div class="main-component">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
Create File Login.vue
Complete Login View and When Logged in set cookie in localStorage, then redirect to path /
Create File Page.vue
Complete Dashboard view with Header and Footer and Include a <router-view> tag
In main.js, Use this type of logic to check user is logged in before each transition & if server gives 401 status on api call, then redirecting to login page
router.beforeEach(function (to, from, next) {
console.log('beforeEach', to.path + ' - Auth: ' + auth.user.authenticated)
if ((to.path !== '/login' && to.path !== 'login') && !auth.user.authenticated) {
next({ path: '/login' })
} else if ((to.path === '/login' || to.path === 'login') && auth.user.authenticated) {
next({ path: '/' })
} else {
next()
}
})
// Whenerver Server Gives 401 Status Code, it logouts and redirect to login page
Vue.http.interceptors.push(function (request, next) {
next(function (response) {
if (response.status === 401) {
let msg = response.body.returnMessage
localStorage.setItem('logoutReason', msg)
auth.logout()
}
})
})
auth.user.authenticated is variable to check whether token exists in localstorage or not