Get children from current route - vue.js

I have a vuejs 2 project and searching for a way to get the children of the current route (using typescript).
Wheather the this.$route nor the this.$router.currentRoute contains a children-prop.
is there any way?
With this information I would like to archive to generate tabs (subviews) automatically.
Update 1
Ok, the solution to get the child-routes is as follows:
this.$router.options.routes?.find((route) => route.name === this.$route.name)?.children
Now the challange is, that comming from a child root I first need to get the parent. Is there any way to also get the parent respectively the get the children of the root-Route?
So I need all children of the current or if available of the parent/root route.
Update 2
Ok, I currently came up with following solution:
this.$router.options.routes?.find((route) => route.name === this.$route.name || route.children?.find((child) => child.name === this.$route.name))?.children
if there is any cleaner/better solution please let me know.

Following my current solution:
<template>
<div class="router-tabs">
<div class="tabs">
<router-link v-for="(route, index) in childRoutes" :key="index" class="tab" :to="route">
{{ route.props.label }}
</router-link>
</div>
<router-view />
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
#Component
export default class RouterTabs extends Vue {
public get childRoutes() {
return this.$router.options.routes?.find((route) => route.name === this.$route.name || route.children?.find((child) => child.name === this.$route.name))?.children;
}
}
</script>
that works with following route-config:
{
path: '/tabs',
name: 'Tabs',
component: Tabs,
children: [
{
path: 'test',
name: 'Test-View',
component: Test,
props: { label: 'Test' }
},
{
path: 'test-two',
name: 'Test-View-Two',
component: TestTwo,
props: { label: 'Test12' }
}
]
},

Related

Vue 3 how to get id of selected tree Node and pass as params with API id

I'm using tree view for my application and I'm getting the id of each when checkbox selected as how it is here:
<blocks-tree class="border-bottom" :data="treeData" :horizontal="treeOrientation==='0'"
:collapsable="true">
<template #node="{data}">
<span>
<input type="checkbox"
:checked="selected.indexOf(data.some_id)> -1"
#change="(e)=>toggleSelect(data,e.target.checked)"/>
{{data.label}}
</span>
</template>
</blocks-tree>
<button class="btn" #click="editState(selected)">
ایجاد ساختمان
<br>
Selected: {{selected}}
</button>
the toggle select is like:
const toggleSelect = (node, isSelected) => {
isSelected ? selected.value.push(node.some_id) : selected.value.splice(selected.value.indexOf(node.some_id), 1);
if(node.children && node.children.length) {
node.children.forEach(ch=>{
toggleSelect(ch,isSelected)
})
}
}
now I want to get the id of the selected node with #click and pass it as params to another route.
can anyone help me with it please?
editState(id) {
this.$router.push({name: 'RealStateForm', params: {id: id}})
}
actually, it relates to Vue-router and in Vue 2 I did like this:
I have a route with these params:
{
path: "/natural-users/:userId?/:committeeId?",
components: {
main: Registration,
top: Top,
right: Right
},
beforeEnter: (to, _from, next) => {
if (!ability.isSelfUser(to.params.userId)) next("/");
next();
},
props: { main: true }
and when I want to redirect to this route I do like this:
this.$router.push(`/natural-users/${this.userID}/${this.commitee}`)
I hope this helps you.

VueJS Render Dynamic Component with route query

I am trying to have render different components based on the route query produced by clicking tabs. I have tried a dynamic component approach but it is working only on initial load. When I switch by clicking tabs which changes the route query it does not change to the other component. Below are my code and files
Show.vue
<div>
<b-container fluid class="bg-white">
<b-row class="topTab types" v-if="$refs.chart">
<b-col
:class="{ active: currentTab === index }"
v-for="(tabDisplay, index) in $refs.chart.tabDisplays"
:key="index"
>
<router-link
:to="{ query: { period: $route.query.period, tab: index } }"
>
{{ tabDisplay }}
</router-link>
</b-col>
</b-row>
</b-container>
<component v-bind:is="currentExample" ref="chart" />
</div>
export default {
props: {
group: String,
cp: String,
name: String,
id: [Number, String]
},
computed: {
currentExample() {
return () =>
import(
`#/components/Trend/example/Charts/${this.group}/${this.id}/Base.vue`
);
},
}
}
Below is the component used above snippet
Base.vue
<template>
<component v-bind:is="currentData"> </component>
</template>
<script>
export default {
data: function() {
return {
tabDisplays: {
1: "example1",
2: "example2",
3: "example3",
4: "example4",
5: "example5",
6: "example6"
}
};
},
computed: {
currentData() {
return () =>
import(
`#/components/Trend/example/Charts/${this.$route.params.group}/${
this.$route.params.id
}/Tab${this.$route.query.tab === "6" ? "6" : "1-5"}.vue`
);
}
}
};
</script>
I am expecting that if I click the example6 link. It will show my #/components/Trend/example/Charts/${this.group}/${this.id}/Tab6.vue on the <component>
Dynamic components :is expects and identifier not a fully fledged component.
You should import the component under the components property in your parent component and then use the computed property to only get the identifier.
components: {
'comp{group}{id}{tab}': () => import(
`#/components/Trend/example/Charts/{group}/{id}/Tab{tab}.vue`)
}
Note: {group}, {id} and {tab} are only placeholders for your actual values. You need to import all of them.
And use the currentData to only get the identifier:
computed: {
currentData() {
return `comp${this.$route.params.group}${this.$route.params.id}{this.$route.query.tab === "6" ? "6" : "1-5"}`
}
}
I have fixed it by just being a simple.
Base.vue
<template>
<component v-bind:is="currentData"> </component>
</template>
<script>
import Tab from "#/components/Trend/example/Charts/abc/1/Tab1-5.vue";
import Tab6 from "#/components/Trend/example/Charts/abc/1/Tab6.vue";
export default {
computed: {
currentData() {
if (this.$route.query.tab === "6") {
return Tab6;
} else {
return Tab;
}
}
}
};
</script>
My structure allows me to be simple and straightforward on this level

Read props from parent component in a list of dynamic components

I have a problem to read the passed data via props in Vue.js from parent to child. I have a list of components
components: [
{
name: "Cart Overview",
component: "CartOverview",
props: this.shopperCart
},
{
name: "Bank Account Overview",
component: "BankAccountOverview",
props: {}
},
{ name: "Verification", component: "Verification", props: {} },
{
name: "Completion Message",
component: "CompletionMessage",
props: {}
}
]
The variable "shopperCart" is set by a request from a backend.
Template of the parent component
<div class="modal-body">
<component
:is="checkoutComponents[currentStep].component"
v-bind="checkoutComponents[currentStep].props"
></component>
</div>
The user can navigate through the components with a next step button who sets the variable currentStep.
Example of one child component
<template>
<div>
<h1>Cart Oerview</h1>
{{ shopperCart }}
</div>
</template>
<script>
export default {
name: "CartOverview",
props: {
shopperCart: { type: Object, default: () => {} }
},
mounted() {
console.log("shopperCart", this.shopperCart);
}
};
</script>
The components lie on a modal. The log output only shows up displaying undefined when I refresh the page, where I can open the modal.
Can someone please help me?
Best regards,
A. Mehlen
I found myself a solution. I changed in the parent component the v-bind with :data
<div class="modal-body">
<component
:is="checkoutComponents[currentStep].component"
:data="checkoutComponents[currentStep].props"
></component>
</div>
and in the child component the name of the prop
<template>
<div>
<h1>Cart Oerview</h1>
{{ data }}
</div>
</template>
<script>
export default {
name: "CartOverview",
props: {
data: { type: Object, default: () => {} }
},
mounted() {
console.log("shopperCart", this.data);
}
};
</script>
Now it works :-)

how to programmatically return to Vue cli's pre-made Home.vue

I'm using Vue CLI 3 and it makes a few routes. One is Home.vue. In my program I am trying to programmaticaly go to different pages. I added the routes I need in router.js but kept the already created routes for Home.vue and About.vue. It works fine until I get to 'Home' and get a warning: [vue-router] Route with name 'Home' does not exist.'
Here is the code:
<template>
<div class='secondItem'>
<h4 v-for="item in menuItems"
#click="bindMe(item)" v-bind:class="{'active':(item === current)}">{{item}}</h4>
</div>
</template>
<script>
export default {
name: 'Header',
data() {
return {
current: '',
menuItems: ['Home', 'About', 'Portfolio', 'Contact'],
}
},
methods: {
bindMe(item) {
this.current = item;
this.$router.push({
path: item
})
}
}
}
<script>
Are you using named routes? In that case you need to use name instead of path:
this.$router.push({
name: item
})
Also, your example can be simplified quite a lot. Try this:
<template>
<div class="secondItem">
<router-link :to="{ name: item }" tag="h4" active-class="active" v-for="item in menuItems" v-bind:key="item">{{item}}</router-link>
</div>
</template>
<script>
export default {
name: 'Header',
data() {
return {
menuItems: ['Home', 'About', 'Portfolio', 'Contact']
}
}
}
<script>

Conditional <router-link> in Vue.js dependant on prop value?

Hopefully this is a rather simple question / answer, but I can't find much info in the docs.
Is there a way to enable or disable the anchor generated by <router-link> dependent on whether a prop is passed in or not?
<router-link class="Card__link" :to="{ name: 'Property', params: { id: id }}">
<h1 class="Card__title">{{ title }}</h1>
<p class="Card__description">{{ description }}</p>
</router-link>
If there's no id passed to this component, I'd like to disable any link being generated.
Is there a way to do this without doubling up the content into a v-if?
Thanks!
Assuming you want to disable anchor tag as in not clickable and look disabled the option is using CSS. isActive should return true by checking prop id.
<router-link class="Card__link" v-bind:class="{ disabled: isActive }" :to="{ name: 'Property', params: { id: id }}">
<h1 class="Card__title">{{ title }}</h1>
<p class="Card__description">{{ description }}</p>
</router-link>
<style>
.disabled {
pointer-events:none;
opacity:0.6;
}
<style>
If you want to just disable the navigation , you can use a route guard.
beforeEnter: (to, from, next) => {
next(false);
}
If you need to use it often, consider this:
Create new component
<template>
<router-link
v-if="!disabled"
v-bind="$attrs"
>
<slot/>
</router-link>
<span
v-else
v-bind="$attrs"
>
<slot/>
</span>
</template>
<script>
export default {
name: 'optional-router-link',
props: {
params: Object,
disabled: {
type: Boolean,
default: false,
},
},
};
</script>
Optional, register globally:
Vue.component('optional-router-link', OptionalRouterLink);
Use it as follows:
<optional-router-link
:disabled="isDisabled"
:to="whatever"
>
My link
</optional-router-link>
The problem is that router-link renders as an html anchor tag, and anchor tags do not support the disabled attribute. However you can add tag="button" to router-link:
<router-link :to="myLink" tag="button" :disabled="isDisabled" >
Vue will then render your link as a button, which naturally supports the disabled attribute. Problem solved! The downside is that you have to provide additional styling to make it look like a link. However this is the best way to achieve this functionality and does not rely on any pointer-events hack.
I sometimes do stuff like this:
<component
:is="hasSubLinks ? 'button' : 'router-link'"
:to="hasSubLinks ? undefined : href"
:some-prop="computedValue"
#click="hasSubLinks ? handleClick() : navigate"
>
<!-- arbitrary markup -->
</component>
...
computed: {
computedValue() {
if (this.hasSubLinks) return 'something';
if (this.day === 'Friday') return 'tgif';
return 'its-fine';
},
},
But I basically always wrap router-link, so you can gain control over disabled state, or pre-examine any state or props before rendering the link, with something like this:
<template>
<router-link
v-slot="{ href, route, navigate, isActive, isExactActive }"
:to="to"
>
<a
:class="['nav-link-white', {
'nav-link-white-active': isActive,
'opacity-50': isDisabled,
}]"
:href="isDisabled ? undefined : href"
#click="handler => handleClick(handler, navigate)"
>
<slot></slot>
</a>
</router-link>
</template>
<script>
export default {
name: 'top-nav-link',
props: {
isDisabled: {
type: Boolean,
required: false,
default: () => false,
},
to: {
type: Object,
required: true,
},
},
data() {
return {};
},
computed: {},
methods: {
handleClick(handler, navigate) {
if (this.isDisabled) return undefined;
return navigate(handler);
},
},
};
</script>
In my app right now, I'm noticing that some combinations of #click="handler => handleClick(handler, navigate)" suffer significantly in performance.
For example this changes routes very slow:
#click="isDisabled ? undefined : handler => navigate(handler)"
But the pattern in my full example code above works and has no performance issue.
In general, ternary operator in #click can be very dicey, so if you get issues, don't give up right away, try many different ways to bifurcate on predicates or switch over <component :is="" based on state. navigate itself is an ornery one because it requires the implicit first parameter to work.
I haven't tried, but you should be able to use something like Function.prototype.call(), Function.prototype.apply(), or Function.prototype.bind().
For example, you might be able to do:
#click="handler => setupNavigationTarget(handler, navigate)"
...
setupNavigationTarget(handler, cb) {
if (this.isDisabled) return undefined;
return this.$root.$emit('some-event', cb.bind(this, handler));
},
...
// another component
mounted() {
this.$root.$on('some-event', (navigate) => {
if (['Saturday', 'Sunday'].includes(currentDayOfTheWeek)) {
// halt the navigation event
return undefined;
}
// otherwise continue (and notice how downstream logic
// no longer has to care about the bound handler object)
return navigate();
});
},
You could also use the following:
<router-link class="Card__link" :to="id ? { name: 'Property', params: { id: id }} : {}">
<h1 class="Card__title">{{ title }}</h1>
<p class="Card__description">{{ description }}</p>
</router-link>
If id is undefined the router won't redirect the page to the link.
I've tried different solutions but only one worked for me, maybe because I'm running if from Nuxt? Although theoretically nuxt-link should work exactly the same as router-link.
Anyway, here is the solution:
<template>
<router-link
v-slot="{ navigate }"
custom
:to="to"
>
<button
role="link"
#click="onNavigation(navigate, $event)"
>
<slot></slot>
</button>
</router-link>
</template>
<script>
export default {
name: 'componentName',
props: {
to: {
type: String,
required: true,
},
},
methods: {
onNavigation(navigate, event) {
if (this.to === '#other-action') {
// do something
} else {
navigate(event);
}
return false;
},
};
</script>