beforeRouteEnter doesn't work in vue child component - javascript

I try to detect what is the previous route in my component. I use beforeRouteEnter to find it. it works in CreditAdd.vue but when I use beforeRouteEnter in Back.vue it doesn't work!
I think it because the Back component is a child. but I can't solve the problem
Back.vue:
<template>
<i class="bi ba-arrow-right align-middle ml-3" style="cursor: pointer" #click="handleBack"></i>
</template>
<script>
export default {
name: 'Back',
data() {
return {
id: null
};
},
beforeRouteEnter(to, from, next) {
console.log('please log');
next(vm => {
vm.fromRoute = from;
});
},
mounted() {},
methods: {
handleBack() {
if (!this.fromRoute.name) {
this.$router.push({ name: 'dashboard' });
} else {
this.$router.back();
}
}
}
};
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
CreditAdd.vue:
<template>
<Layout>
<template slot="header">
<Back />
<span>افزایش اعتبار</span>
</template>
<div class="col-lg-10 px-0">
<Card>
<template v-if="type == 'select'">
<credit-add-way #select="changeType"></credit-add-way>
</template>
<template v-if="type == 'bank'">
<credit-add-bank-way #back="changeType('select')"></credit-add-bank-way>
</template>
<template v-if="type == 'code'">
<credit-add-code-way #back="changeType('select')"></credit-add-code-way>
</template>
</Card>
</div>
</Layout>
</template>

beforeRouteEnter and other navigation guards should only works on the Vue file that is defined the link in the router, thats why Back.vue not working.
You can use plain javascript to get the previous URL
in Back.vue
mounted() {
console.log(document.referrer);
}
Another way is you can store the previous route in Vuex store,
In AddCredit.vue where navigation guards work
beforeRouteEnter(to, from, next) {
// store this in vuex
},
then in Back.vue can just retrieve right away from the store

Related

I want to handle components asynchronously in Nuxt.js and display alert messages

What I want to come true
I want to display an alert message considering the result of the data sent to the server.
However, since alert messages are managed by another component, it is necessary to call the component asynchronously.
The official Vue.js documentation used Vue.component, but what's the right way to do it with Nuxt.js?
Code
I want to use search.vue in success.vue
search.vue
<template>
<v-app>
<div
class="teal lighten-1 background pa-10"
>
<!-- <div
v-if="responseBook === 200"
> -->
<alert-success />
<v-sheet
width="1100px"
class="mx-auto pa-5 rounded-xl"
color="grey lighten-5"
min-height="500px"
>
<!-- 書籍検索、表示 -->
<BookPostDialog />
<!-- 選択されたデータの表示 -->
<BookPostSelected />
</v-sheet>
</div>
</v-app>
</template>
<script>
export default {
computed: {
responseBook () {
return this.$store.state.book.responseBook.status
}
}
}
</script>
<style lang="scss" scoped>
.background {
background-image: url('~/assets/images/tree.png');
background-repeat: space repeat;
}
</style>
Alert/success.vue
<template>
<v-alert type="success">
Succeeded
</v-alert>
</template>
If you want to use that kind of feature, you'll be better suited looking for something like this component: https://buefy.org/documentation/toast
Or anything like this in the jungle of CSS frameworks, pretty sure each of them have one.
Or implement it yourself, for this, you need to rely on portals.
For Vue2, this is how to do achieve it: https://portal-vue.linusb.org/guide/getting-started.html#enabling-disabling-the-portal
<portal to="destination" :disabled="true">
<p>
Your content
</p>
</portal>
If you want to show success.vue component after the connection to server (getting or posting data), you can use v-if as follows:
search.vue
<template>
<div>
<p>search compo</p>
<div v-if="this.$store.state.book.responseBook == 'ok'">
data was received.
<success />
</div>
</div>
</template>
<script>
export default {
mounted() {
this.$store.dispatch('getData')
}
}
</script>
success.vue
<template>
<div>
succeess compo
</div>
</template>
And then in your store/index.js file:
import Vuex from "vuex";
const createStore = () => {
return new Vuex.Store({
state: {
book: {
responseBook: ""
}
},
mutations: {
bookMutate(state, data) {
state.book.responseBook = data;
}
},
actions: {
getData(vuexContext) {
let vue = this;
// your request is here
setTimeout(function() {
vue.$axios.$get("https://pokeapi.co/api/v2/pokemon/ditto").then((result) => {
console.log(result);
vuexContext.commit("bookMutate", "ok");
}).catch(err => {
console.log(err);
})
}, 10000)
},
}
});
};
export default createStore;
I intentionally used setTimeout() in my action to see that the success component is loaded after the data was received. in actual situation it is better to use this action:
getData(vuexContext) {
this.$axios.$get("https://pokeapi.co/api/v2/pokemon/ditto").then((result) => {
console.log(result);
vuexContext.commit("bookMutate", "ok");
}).catch(err => {
console.log(err);
})
},
I used axios for calling the api but you can use your own method of getting data. but after that you must commit the mutation to change the state.

Vue JS - How to get props value in parent component

I have three components component-1, component-2, and an App component, I pass a Boolean props from component-1 to component-2 then using #click event I change the props value from true to false and vice versa
App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" width="20%" />
<component1 />
</div>
</template>
component-1.vue
<template>
<div>
<component2 :have-banner="true" />
</div>
</template>
<script>
import component2 from "./component-2";
export default {
components: {
component2,
},
};
</script>
component-2.vue
<template>
<div>
<button #click="AssignBanner = !AssignBanner">Click me</button>
<p>{{ AssignBanner }}</p>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
haveBanner: Boolean,
},
data() {
return {
AssignBanner: this.haveBanner,
};
},
};
</script>
I want to get the value of the props in component-1, that is, I want to track the changing value in component-1, since I want to write some logic, but I can’t keep track of the value in component-1.
You can see the given code in codesandbox
Looks like you want to achieve two-way binding for the prop haveBanner. You can achieve this with the .sync modifier if you are using Vue 2.3+.
component-1.vue
<template>
<div>
<component2 :have-banner.sync="haveBanner" />
</div>
</template>
<script>
import component2 from "./component-2";
export default {
components: {
component2,
},
data() {
return {
haveBanner: true,
}
},
};
</script>
component-2.vue
<template>
<div>
<button #click="assignBanner = !assignBanner">Click me</button>
<p>{{ assignBanner }}</p>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
haveBanner: Boolean,
},
data() {
return {
assignBanner: this.haveBanner,
};
},
watch: {
assignBanner(value) {
// propagate to parent component
this.$emit('update:haveBanner', value)
},
},
};
</script>

Vue Router Universal Navbar Search

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>

Vuejs : Rendering components conditionally based on route

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.

vue - How a normal link could act like <route-link>?

I have two pages routed:
Home > Results
Inside 'Home', I have a button/link which is not in <route-view/> and I want this button to redirect to Results.vue passing a parameter.
This parameter named activeTab, has to open the desired vue-tabs, which I can't accomplish, because it's getting nothing from variable:
code:
Home.vue
<div class="row">
<Notes refName="Relationship" />
<Notes refName="Support" />
</div>
...
<script>
import Notes from '#/components/Monthlynotes.vue'
export default {
name: 'home',
components: {
Notes
},
</script>
/components/Monthlynotes.vue
<b-card>
<p class="card-text">{{ refName }}</p>
<b-link class="right" :activeTab="refName" href="/results">More</b-link>
</b-card>
...
<script>
export default {
props: {
refName: String,
},
</script>
Results.vue
<vue-tabs type="pills" v-model="tabName">
<v-tab title="Relationship">
<RelDT msg="Results view"/>
</v-tab>
<v-tab title="Support">
<SupDT msg="Results view"/>
</v-tab>
</vue-tabs>
...
<script>
import RelDT from '#/components/DataTable.rel.vue'
import SupDT from '#/components/DataTable.sup.vue'
export default {
name: 'results',
props: {
activeTab: String
},
components:
{
RelDT,
SupDT,
},
data() {
return {
tabName: activeTab
}
}
}
</script>
App
<router-link :to="{name:'results', param:{activeTab}}">Results</router-link>
How can I make this <b-link> route if it was a <route-link />?
Even the b-link component supports the :to property. To be found here
The value of the property will be passed to router.push().
<b-link :to="{name:'results', param:{activeTab}}">Redirect me</b-link>

Categories