This is my first Vue.js component, I try to emit event from it. But my browser doesnt see 'updateValue' method... Whats wrong?
<template>
<div>
<ul class="nav flex-column">
<li v-for="item in items">
<div class="nav-link" onclick="updateValue(1)">
{{ item.name }}
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['items'],
methods: {
updateValue: function (value){
this.$emit('change', value);
}
}
}
</script>
In Vue you should use #click or v-on:click instead of onclick.
Related
When a prop has the same name as a sub-element in a v-for directive. How to access it?
<template>
<ul>
<li v-for="{ id, name } in data" :key="id">
{{name}} and {{props.name}} // Doesn't work, and props name is shadowed here.
</li>
</ul>
</template>
<script setup lang="ts">
const props = defineProps({ data: Array, name: String });
</script>
Don't destructure it:
<template>
<ul>
<li v-for="item in data" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
I'm trying to share data stored in a variable favorite_count within the Favorites component in Favorites.vue file. I want to share that data with the App Component in the App.vue file but I'm not able to. I would want that if I change the value of favorite_count in the Favorites component, it changes in the App Component. Done quite some research on the web but no success yet. Any ideas on what I could be doing wrong?
Favorites.vue file
<template>
<div class="row m-5">
<h3 class="col-8">Your Favorites</h3>
<div class="col-4">
<p class="pull-right m-1">Picks
<span >{{ favorite_count }}</span> / 5</p>
</div>
<hr>
</div>
</template>
<script>
export default {
name: 'favorites',
data() {
return {
favorite_count: 5,
}
},
methods: {
changeFavoriteCount() {
this.favorite_count = this.favorite_count + 2;
},
emitToParent (event) {
this.$emit('childToParent', this.favorite_count)
}
}
}
</script>
App.vue file
<template>
<div class="navbar navbar-expand-md navbar-dark bg-primary">
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav">
<li class="nav-item">
<router-link to="/favorites" class="btn btn-info">
Favorites ( {{ favorite_count }} )
</router-link>
</li>
</ul>
</div>
</div>
</template>
<script>
import Favorites from './components/Favorites.vue'
export default {
name: 'App',
components: {
Favorites
},
data () {
return {
favorite_count: 0,
}
}
}
</script>
If you are going to use <router-view> later in your application, I would suggest this solution
If you were going to include Favorites inside <template> in App.vue, you can use props:
1. Declare your 'shared' variable in the parent component (App.vue)
data () {
return {
favorite_count: 0,
}
},
2. Define props in your child component (Favorites.vue)
export default {
props: { favorite_count: Number },
...
}
3. Pass favorite_count as prop to Favorites
<template>
...
<Favorites :favorite_count="favorite_count" ... />
</template>
If you will need to update favorite_count - emit an event to parent component. More about it in Vue docs
Edit: Just to clarify: If you are going to update favorite_count from Favorites.vue, you need to emit an event to App.vue to avoid mutating props.
That also means you need to move your changeFavoriteCount() function to App.vue and apply a listener to your child component which will call this function:
// App.vue
<template>
...
<Favorites
#your-update-event="changeFavoriteCount"
:favorite_count="favorite_count" ...
/>
</template>
...
changeFavoriteCount(newVal) {
this.favorite_count = newVal;
},
change your Favourite.vue file like this
<template>
<div class="row m-5">
<h3 class="col-8">Your Favorites</h3>
<div class="col-4">
<p class="pull-right m-1">
Picks <span>{{ favorite_count }}</span> / 5
<button #click="changeFavoriteCount">Click me to change favorite_count</button>
</p>
</div>
<hr />
</div>
</template>
<script>
export default {
name: "favorites",
data() {
return {
favorite_count: 5,
};
},
methods: {
changeFavoriteCount() {
this.favorite_count = this.favorite_count + 2;
this.emitToParent();
},
emitToParent() {
this.$emit("childToParent", this.favorite_count);
},
},
};
</script>
and the App.vue file like this
<template>
<div class="navbar navbar-expand-md navbar-dark bg-primary">
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav">
<li class="nav-item">
<router-link to="/favorites" class="btn btn-info">
<Favorites #childToParent="updateFavorite" />
Favorites ( {{ favorite_count }} )
</router-link>
</li>
</ul>
</div>
</div>
</template>
<script>
import Favorites from './components/Favorites.vue'
export default {
name: 'App',
components: {
Favorites
},
data () {
return {
favorite_count: 0,
}
},
methods: {
updateFavorite(data) {
this.favorite_count = data;
},
},
}
</script>
I am disabling document body scroll when my mobile nav is open which is working as expected. However it was not removing the overflow hidden when user would click a link to another route/page. I created a simple method to remove the overflow hidden when a link is clicked in the nav when the menu is open, which does work with one small caveat. When the user is on say page "home" and the mobile nav is OPEN, when they click the link "home" inside of the mobile nav it is closing the menu, and I understand I have done that with the method I created. Is there a way to prevent that event from firing when clicking the link of the page you are on?
<header :class="{ 'header-active': activeHamburger }">
<nav class="nav-row nav-row--primary" aria-label="Main Navigation">
<ul class="row--flex justify--space-between">
<li>
<router-link to="/" #click="removeOverflowHidden();
">
home
</router-link>
</li>
<li>
<router-link to="About" #click="removeOverflowHidden();
">
about
</router-link>
</li>
<li>
<router-link to="Work" #click="removeOverflowHidden();
">
work
</router-link>
</li>
<li>
<router-link to="Contact" #click="removeOverflowHidden();
">
contact
</router-link>
</li>
</ul>
</nav>
</header>
data() {
return {
activeHamburger: false
};
},
watch: {
activeHamburger: function() {
if (this.activeHamburger) {
document.documentElement.style.overflow = "hidden";
return;
}
document.documentElement.style.overflow = "auto";
}
},
methods:{
removeOverflowHidden() {
this.activeHamburger = false;
}
}
You can pass the route value to the method and check that it's not same as the current route before executing.
<template>
<header :class="{ 'header-active': activeHamburger }">
<nav class="nav-row nav-row--primary" aria-label="Main Navigation">
<ul class="row--flex justify--space-between">
<li>
<router-link to="/" #click="removeOverflowHidden('home');
">
home
</router-link>
</li>
<li>
<router-link to="About" #click="removeOverflowHidden('about');
">
about
</router-link>
</li>
<li>
<router-link to="Work" #click="removeOverflowHidden('work');
">
work
</router-link>
</li>
<li>
<router-link to="Contact" #click="removeOverflowHidden('contact');
">
contact
</router-link>
</li>
</ul>
</nav>
</header>
</template>
<script>
export default {
data() {
return {
activeHamburger: false
};
},
watch: {
activeHamburger: function() {
if (this.activeHamburger) {
document.documentElement.style.overflow = "hidden";
return;
}
document.documentElement.style.overflow = "auto";
}
},
methods:{
removeOverflowHidden(value) {
if (this.$route.path !== value) {
this.activeHamburger = false;
}
}
}
}
</script>
I haven't seen your routes but you can also use this.$route.name if you prefer and adjust accordingly the values you pass to the method.
I've been working on a sub menu system for vue.js that gets populated by the current route's children. I recently posted a question about it and it was answered here.
Now I'm trying to improve upon that but I'm having trouble finding out how to get a component's path or namespace (not sure what word to use). Currently I see what I want in the Vue Dev tools I just don't now how to get those properties.
I've tried {{$route.path}} but that only gives me the full path.
Another thing I've tried which kind of helps is storing the current path the first time I load the menu. Which preserves the path I want to be appending to. The only issue is when i navigate directly to the page it loads the menu with the pages url which then breaks the functionality.
Here is the code:
<template>
<nav id="sidebar">
<div class="sidebar-header">
<h3>Bootstrap Sidebar</h3>
</div>
<h2>Route: {{ }}</h2>
<ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==path)">
<li v-for="child in route.children">
<a class="nav-item" :key="index">
<router-link :to="{path: path+'/'+child.path}" exact-active-class="active">
<icon :icon="route.icon" class="mr-2" /><span>{{ child.path }}</span>
</router-link>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
data() {
return {
path: this.$route.path
}
},
methods: {
},
}
</script>
I really want something closer to this though, where instead of using $route.path to return the full path like /traveler/Create I want something to just return /traveler or whatever the path for it's router-view is:
<template>
<nav id="sidebar">
<div class="sidebar-header">
<h3>Bootstrap Sidebar</h3>
</div>
<ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.path)">
<li v-for="child in route.children">
<a class="nav-item" :key="index">
<router-link :to="{path: $route.path+'/'+child.path, params: { idk: 1 }}" exact-active-class="active">
<icon :icon="route.icon" class="mr-2" /><span>{{ child.path }}</span>
</router-link>
</a>
</li>
</ul>
</nav>
</template>
<script>
import { travelerroutes } from '../../router/travelerroutes'
export default {
data() {
console.log(travelerroutes);
return {
travelerroutes,
collapsed: true
}
},
methods: {
toggleCollapsed: function (event) {
this.collapsed = !this.collapsed
}
}
}
</script>
To get the path of the current component I had to just use the $Route.matched property. In my case because I didn't want to include the childrens' paths I used the first match like this $Route.matched[0].path
you can learn more about it here
I also used it to update my other question/answer
Essentially you can use it in a template like this:
<template>
<nav id="sidebar">
<div class="sidebar-header">
<h3>Bootstrap Sidebar</h3>
</div>
<ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.matched[0].path)">
<li v-for="child in route.children">
<a class="nav-item" :key="index">
<router-link :to="route.path+'/'+child.path" exact-active-class="active">
<icon :icon="route.icon" class="mr-2" /><span>{{ child.name }}</span>
</router-link>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
data() {
return {
}
}
}
</script>
I have a structure like this:
<childs>
<child>
<ul>
<li v-for="item in currentData">#{{ item.name }}</li>
</ul>
</child>
</childs>
In the child component, I have a data property currentData.
// Child.vue
data: {
currentData: {}
}
For some reason, I am assigning value to this currentData prop, from the childs component (not from child).
// Childs.vue
child.currentData = data;
How do I make this currentData available to the slotted elements of <child>:
<ul>
<li v-for="item in currentData">#{{ item.name }}</li>
</ul>
The template for Child.vue is like this:
<template> <div><slot></slot></div> </template>
I tried something like this:
<template> <div><slot :current-data="currentData"></slot></div> </template>
I belive what you need is Scoped Slots.
For that you should explicitly pass (in the slot declaration at the template) what props you want to make available to the <slot> "user".
E.g. say you want to make a foo property available to the slot users of <child> (assuming childData property existi in <child>). You would do:
<!-- This is <child>'s template -->
<template> <div><slot :foo="childData"></slot></div> </template>
From that point on, whoever uses that <child> component can access that foo property by declaring slot-scope:
<child>
<ul slot-scope="slotProps">
<li>{{ slotProps.foo }}</li>
</ul>
</child>
Notice that the slot-scope is declared in the element that takes the place where the <slot> was.
Full demo:
Vue.component('children', {
template: '#children'
})
Vue.component('child', {
template: '#child',
data() {
return {
childData: "I am childData"
}
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<children>
<child>
<ul slot-scope="slotProps">
<li>{{ slotProps.foo }}</li>
<!-- <li v-for="item in currentData">#{{ item.name }}</li> -->
</ul>
</child>
</children>
</div>
<template id="children">
<div><slot></slot></div>
</template>
<template id="child">
<div><slot :foo="childData"></slot></div>
</template>
What if I wanted to add another element outside the <ul> element? Vue simply discards anything outside slot-scope.
This is not due to slot-scope, but to <slot>s in general.
Since child has only one <slot>, the first element you place within <child> is the one that will take the slot.
If you want to have multiple elements take the slot, you'll have to wrap them. E.g. in a <div>. But, if you don't want this wrapper element to be rendered, use a <template>. See demo below.
Vue.component('children', {
template: '#children'
})
Vue.component('child', {
template: '#child',
data() {
return {
childData: "I am childData"
}
}
})
new Vue({
el: '#app'
})
.child { border: 1px solid red }
<script src="https://unpkg.com/vue"></script>
<div id="app">
<children>
<child>
<template slot-scope="slotProps">
<ul>
<li>{{ slotProps.foo }}</li>
<!-- <li v-for="item in currentData">#{{ item.name }}</li> -->
</ul>
<span>howdy</span>
</template>
</child>
</children>
</div>
<template id="children">
<div><slot></slot></div>
</template>
<template id="child">
<div class="child"><slot :foo="childData"></slot></div>
</template>