I've seen this question multiple times, but without a good response
I have a topmenu with links and I want to trigger a function when I click the link of the page that I'm on right now
How do I achieve this?
Remember the in-component guards wont trigger because the route does not change
You would want to trigger a function on all links and trigger the navigate from in there passed by a param.
Then check if the current page URL matches the passed param.
E.g.
if (this.$router.currentRoute == 'param passed') {
return someOtherFucntion();
}
return this.$router.push('param passed');
Its called programmatic navigation you can see more here.
https://router.vuejs.org/guide/essentials/navigation.html#programmatic-navigation
I believe I found a more elegant solution
I made a wrapper for links
<template>
<a :href="resolvedRoute.route.fullPath" :class="{ 'is-active': resolvedRoute.route.name == $route.name }" #click.prevent="clicked">
<slot></slot>
</a>
</template>
<script>
import EventBus from '#/event-bus'
export default {
name: 'menu-link',
props: {
to: {
type: Object,
default: {}
}
},
computed: {
isExactActive(){
return this.$route.fullPath == this.resolvedRoute.route.fullPath
},
resolvedRoute(){
return this.$router.resolve(this.to)
}
},
methods: {
clicked(){
if(this.isExactActive)
return EventBus.$emit('page-reload', this.resolvedRoute.route)
this.$router.push(this.to)
}
}
}
</script>
Then a simple mixin
import EventBus from '#/event-bus'
export default {
mounted(){
EventBus.$on('page-reload', this.checkPageReload)
},
beforeDestroy(){
EventBus.$off('page-reload', this.checkPageReload)
},
methods: {
checkPageReload(route){
if(route.name == this.$options.name && this.pageReload){
EventBus.$emit('page-loading', true)
this.pageReload(route)
}
}
}
}
I believe this is the optimal solution. I'll just have to replace router-link with menu-link
Related
Is it possible to extend child component function at runtime in vue? I want to limit/stop child component function call based on parent scope logic (I want to avoid passing props in this specific case).
Overriding a component method is not a runtime solution/I can't have access to parent scope.
What I have tried and it does not working:
// Foo.vue
<template>
<button #click="func">Click me</button>
</template>
export default {
methods: {
func() {
console.log('some xhr')
}
}
}
// Bar.vue
<template>
<Foo ref="foo"/>
</template>
export default {
components: {Foo}
mounted() {
this.$nextTick(() => {
this.$refs.foo.func = function() {
console.log('some conditional logic')
this.$refs.foo.func()
}
})
}
}
For this usecase a better implementation would be defining the function in the parent itself and passing it through props. Since props are by default reactive you can easily control it from parent.
// Foo.vue
<template>
<button #click="clickFunction.handler">Click me</button>
</template>
export default {
name: 'Foo',
props: {
clickFunction: {
type: Object,
required: true
}
}
}
// Bar.vue
<template>
<Foo :clickFunction="propObject"/>
</template>
export default {
components: {Foo},
data() {
return {
propObject: {
handler: null;
}
};
}
mounted() {
this.$nextTick(() => {
if(some condition) {
this.propObject.handler = this.func();
} else this.propObject.handler = null;
})
},
methods: {
func() {
console.log('some xhr')
}
}
}
From what I managed to realize:
the solution in the code posted in the question really replaces the func() method in the child component. It's just that Vue has already attached the old method to the html element. Replacing it at the source will have no impact.
I was looking for a way to re-attach the eventListeners to html component. Re-rendering using an index key would not help because it will re-render the component with its original definition. You can hide the item in question for a split second, and when it appears you will receive an updated eventListener. However, this involves an intervention in the logic of the child component (which I avoid).
The solution is the $forceUpdate() method.
Thus, my code becomes the following:
// Foo.vue
<template>
<button #click="func">Click me</button>
</template>
export default {
methods: {
func() {
console.log('some xhr')
}
}
}
// Bar.vue
<template>
<Foo ref="foo"/>
</template>
export default {
components: {Foo}
mounted() {
this.$nextTick(() => {
let original = this.$refs.foo.func; // preserve original function
this.$refs.foo.func = function() {
console.log('some conditional logic')
original()
}
this.$refs.btn.$forceUpdate(); // will re-evaluate visual logic of child component
})
}
}
How can I access a layout- or page-function directly in a component? Is there a special variable like $root or $parent?
I found a way to do this, but it seems dirty. I saw the component structure using Vue DevTool, and I found the layout is the root's child, so I called the layout's function like this:
this.$root.$children[2].getMap()
Is there a cleaner way?
You could use Vue's provide/inject feature for this. For instance, a page/layout could provide the getMap():
<template>
<MapButton />
</template>
<script>
export default {
name: 'MapPage',
provide() {
return {
getMap() {
return { /* map */ }
}
}
}
}
</script>
...and then any child component on that page/layout could inject the method where needed:
<template>
<button #click="getMap">Get map</button>
</template>
<script>
export default {
name: 'MapButton',
inject: {
getMap: {
default() {
return () => {
console.log('no map')
}
}
}
},
mounted() {
console.log('map', this.getMap())
}
}
</script>
This is my approach of calling a method after v-for has done looping. It works perfectly fine but i still want to know if there's a better approach than this or if vue is offering something like v-for callback.
Parent Component
<template>
<loader-component v-show="show_loader" />
<list-component #onRendered="hideLoader"/>
</template>
<script>
export default {
components: {
loaderComponent,
listComponent
},
data() {
return {
show_loader: true
}
}
methods: {
hideLoader() {
this.show_loader = false;
}
}
}
</script>
List Component
<template>
<item-component v-for="(item, key) in items" :isRendered="isRendered(key)" />
</template>
<script>
export default {
prop: ['items'],
methods: {
isRendered(key) {
let total_items = this.items.length - 1;
if (key === total_items) {
this.$emit('onRendered');
}
}
}
}
</script>
I believe there is a misunderstanding in how exactly Vue updates its reactive properties.
I made a little demo for you hoping it clears it up.
https://codesandbox.io/s/prod-star-pzjhc
I would also recommend some reading from the Vue docs computed properties
Destroyed hook is called later than i need.
I tried to use beforeDestroy instead of destroy, mounted hook instead of created. The destroy hook of previous components is always called after the created hook of the components that replaces it.
App.vue
<div id="app">
<component :is="currentComponent"></component>
<button #click="toggleComponent">Toggle component</button>
</div>
</template>
<script>
import A from './components/A.vue';
import B from './components/B.vue';
export default {
components: {
A,
B
},
data: function(){
return {
currentComponent: 'A'
}
},
methods: {
toggleComponent() {
this.currentComponent = this.currentComponent === 'A' ? 'B' : 'A';
}
}
}
</script>
A.vue
<script>
export default {
created: function() {
shortcut.add('Enter', () => {
console.log('Enter pressed from A');
})
},
destroyed: function() {
shortcut.remove('Enter');
}
}
</script>
B.vue
<script>
export default {
created: function() {
shortcut.add('Enter', () => {
console.log('Enter pressed from B');
})
},
destroyed: function() {
shortcut.remove('Enter');
}
}
</script>
Result:
// Click Enter
Enter pressed from A
// now click on toggle component button
// Click Enter again
Enter pressed from A
Expected after the second enter to show me Enter pressed from B.
Please don't show me diagrams with vue's lifecycle, i'm already aware of that, I just need the workaround for this specific case.
Dumb answers like use setTimeout are not accepted.
EDIT: Made some changes to code and description
If you are using vue-router you can use router guards in the component (as well as in the router file) where you have beforeRouteLeave obviously only works where there is a change in route, see here:
https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards
I'm trying to watch page url. I don't use Vue Router.
My final goal is to set page url as input value:
<template>
<div>
<input v-model="pageUrl">
</div>
</template>
<script>
export default {
data() {
return {
pageUrl: window.location.href,
link: ''
}
},
watch: {
pageUrl: function() {
this.link = window.location.href
}
}
}
</script>
The example above doesn't work somewhy.
I've also tried
watch: {
'window.location.href': function() {
this.link = window.location.href
}
},
Input value is being set only once on component render.
What can be wrong?
well, that is exactly the reason you want to use vue-router!
vue can only detect changes in reactive properties: https://v2.vuejs.org/v2/guide/reactivity.html
if you want to react to changes in the url, you have 2 ways:
https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event or
https://developer.mozilla.org/en-US/docs/Web/API/Window/hashchange_event
i would rather use vue-router or a similar plugin.