Vue JS 3 Parent Calling Multiple Children methods - vue.js

I have a parent page for FAQ and child component that collapses the Q&A. But on the FAQ page we have a button to expand all the FAQs. So I am trying to call a method on the child components which we will have a dozen or so of these. But when I click on view all it only opens the last child component. Why is it only hitting the last component and not all of them?
import CollapsiblePanel from '#/components/CollapsiblePanel';
// Imports
import { ref } from 'vue';
const collapsiblePanelRef = ref();
function expand() {
collapsiblePanelRef.value.expandAll()
}
Then the mark up with the child ref...
<a #click="expand">View All</a>
<CollapsiblePanel ref="collapsiblePanelRef">
<template v-slot:title>
Sample title 1
</template>
<template v-slot:content>
Lorem ipsum 1
</template>
</CollapsiblePanel>
<CollapsiblePanel ref="collapsiblePanelRef">
<template v-slot:title>
Sample title 2
</template>
<template v-slot:content>
Lorem ipsum 2
</template>
</CollapsiblePanel>
Then the CollapsiblePanel looks like...
function expandAll() {
isActive.value = true;
}
defineExpose({
expandAll
})

I do not know the exact answer to why only the last opens, but I would rather do this with a prop. In the parent template:
<a #click="expandAll">View All</a>
<CollapsiblePanel :open="openAll">
...
</CollapsiblePanel>
<CollapsiblePanel :open="openAll">
...
</CollapsiblePanel>
Function for the button in the parent:
const openAll = ref(false);
function expandAll() {
openAll.value = !openAll.value;
}
And than in the CollapsiblePanel script setup:
const props = defineProps({openAll: Boolean});
const currentOpen = ref(false);
/// Call with click on child:
const toggleIndividual = () => {
currentOpen.value = !currentOpen.value;
}
/// If prop changes (expand all button is clicked) open child
watch(props.openAll) {
currentOpen.value = true;
}
And in the CollapsiblePanel template a v-if="currentOpen" on the div that needs to open and close. You can put #click="toggleIndividual" on the element that you want to click to expand it.
Now if you toggle expand all, all childs will expand. If you click an individual child it will collaps.
Hope this helps.

Related

how to pass an event from a child component to a parent?

I'm studying vue js3 - I ran into a misunderstanding of linking variables in components. You need to pass the #click event from the child component, so that the value in the parent has changed.
Example:
children component
<div class="btn" id="btn" #click="addToCart(item)"> Add to cart</div>
Parent component
<p >{{cardValue}}</p>
It is necessary to increase the value of {{cardValue}} by 1 when clicking in the child component
As per my understanding, You are working on an e-commerce application where you want to add the items in a cart from a child component but cart items counter is in parent component. If Yes, Then it is necessary to emit an event to parent on click of Add to cart button, So that it will increment the counter by 1.
Live Demo :
const { createApp, ref, defineComponent } = Vue;
const app = createApp({
setup() {
const cartItemsCount = ref(0)
// expose to template and other options API hooks
return {
cartItemsCount
}
}
});
app.component('item', defineComponent({
template: '<button #click="addToCart">Add to Cart</button>',
methods: {
addToCart() {
this.$emit("addToCart");
}
}
}));
app.mount('#app')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="app">
<div>
Items added into the cart : {{ cartItemsCount }}
</div>
<item #add-to-cart="cartItemsCount += 1"></item>
</div>

Mouseover function wont toggle

I am trying to change the hover value on hover, but it doesn't seem to work whenever I hover over the flex element, what am I doing wrong?
html
template(#default="{ toggle, toggled, hover }")
.flex(#click="toggle" #mouseover="hover = !hover" )
box-icon(type="solid" name="chevron-down" v-if="hover")
component
<template lang="pug">
slot(:toggled="toggled" :toggle="toggle" :hover="hover")
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const toggled = ref(false)
const hover = ref(false)
function toggle() {
toggled.value = !toggled.value
}
return { toggled, toggle, hover }
}
}
</script>
#mouseover is not an event in vuejs.
You will have to use #mouseenter and #mouseleave.
Also it seems like you cannot edit a ref value, you will have to create a function in your template that changes that value. That is something I cannot explain myself right now. Nevertheless I made it work.
Here is a working example :
// Test.vue component
<script setup>
const toggled = ref(false)
const hovered = ref(false)
const hover = (value) => hovered.value = value
const toggle = () => toggled.value = ! toggled.value
</script>
<template>
<slot :toggled="toggled" :toggle="toggle" :hovered="hovered" :hover="hover" />
</template>
<Test>
<template #default="{toggle, toggled, hover, hovered}">
<button #click="toggle" #mouseenter="hover(true)" #mouseleave="hover(false)">
<p>Toggled : {{ toggled }}</p>
<p>Hovered : {{ hovered }}</p>
</button>
</template>
</Test>

how do I pass the value from child to parent with this.$emit

What I trying to achieve here is to pass the const randomNumber inside the child component [src/components/VueForm/FormQuestion.vue] that need to be passed to parent component [src/App.vue]. Therefore I use $emit to pass the date, but since this is my first time working with $emit, I am not really sure how to do that. Could someone help me with this.
In order to run this app, I would add a working code snippet. Click on the start button and fill in the input fields. When the input field validates correctly it will pop up the button and if the user clicks on that is should pass the data to the parent. At the end it should be stored inside the App.vue in localStorage, so therefore I want to receive the randomNumber from that child component.
working code snippet here
// child component
<template>
<div class="vue-form__question">
<span class="question" :class="{ big: !shouldShowNumber }"> {{ getRandomNumber() }} </span>
</div>
</template>
<script>
export default {
methods: {
getRandomNumber() {
const randomNumber = Math.floor((Math.random() * 3) + 1);
const question = this.question.question;
this.$emit('get-random-number', question[randomNumber]);
return question[randomNumber];
}
}
};
// parent component
<template>
<div id="app">
<vue-form
:data="formData"
#complete="complete"
#getRandomNumber="newRandomNumber"
></vue-form>
</div>
</template>
<script>
import VueForm from "#/components/VueForm";
import data from "#/data/demo";
export default {
data() {
return {
formData: data
}
},
components: {
VueForm
},
created() {
this.complete()
},
methods: {
complete(data) {
// Send to database here
// localStorage.setItem('questions', data.map(d => d.question[this.randomNumber] + ': ' + d.answer));
},
}
};
</script>
v-on:get-random-number (or the superior short-hand syntax: #get-random-number). Just like you'd listen to any other event, such as #click or #mouseenter.
Though I don't know off the top of my head if dashes are valid in event names. Might have to camelcase it.

How i can run method from child component vuejs

I have parent and chidl component... I need to run child method, when i click on button in parent.
Example code:
Parent
<template>
<child-component></child-component>
<button>Open Modal in child Component (set modal = true in child component)</button>
</template>
Child:
<template>
<div v-if="modal">
<button #click="modal = false">Close</button>
</div>
</template>
<script>
export default {
data() {
return {
modal: false
}
}
}
</script>
You can achieve this via different implementations. The most common one is via emit (another alternative is via dispatching actions if you are using the Redux pattern)
First, you want to catch the even on the parent component and emit an event. So this should be your template.
<template>
<child-component></child-component>
<button #click="click">Open Modal in child Component (set modal = true in child component)</button>
</template>
Then you have to emit an event (from the parent component) on the function called when a click was made.
Something like:
click: function() {
this.$emit('update');
}
Lastly, your child component needs to "hear" that event. You can achieve that with something like that on the created function of the child component:
this.$parent.$on('update', this.updateModal);
this.updateModal is a function on your child component which just flips the value of the boolean.
In vue you can pass a function as a prop. So you could add a function to alternate model within your parent component then pass it into your child so it can be used normally. I'll put a simple example below.
small edit, you can bind a class like a hidden/reveal class using the status which is bound to the modal state
// Html
<div id="app">
<child v-bind:on-click="toggleModal" v-bind:status="modal" />
</div>
// Parent Component
var sample = new Vue({
el: '#app',
data: {
modal: false
},
methods: {
toggleModal() {
this.modal = !this.modal
}
}
});
// Child component with function prop
Vue.component('child', {
props: {
onClick: Function,
status: Boolean
}
template: '<button v-on:click="onClick()">Press Me</div>'
});

Slot doesn't render well on child component VueJS

I'm trying to loop over a component, I fill up slot with some data but they are not rendering well.
Weird behaviors :
Data are displayed but not visible.
In chrome if i toggle the device toolbar in the debug panel, data are now visible.
Changing font-size in the debug panel make my data visible
When i put a Child component outside the loop, the looped ones are rendered well.
Snippet from my parent Component :
<li class="cards__item" v-for="(staffMember, index) in staff">
<card-profil>
<h3 slot="title">{{staffMember.name}}</h3>
</card-profil>
</li>
Snippet from my child Component :
<template>
<section class="card-profil">
<figure class="fig-card-profil">
<figcaption class="figcaption-card-profil">
<slot name="title"></slot>
</figcaption>
</figure>
</section>
</template>
I get my data this way in my parent component:
export default {
data: function () {
return {
staff: []
}
},
mounted () {
this.getStaff()
},
methods: {
getStaff: async function () {
const staff = await axios({ url: 'https://randomuser.me/api/?results=8' })
this.staff = staff.data.results
}
}
}
Is this problem of lifehook ? Do i have to use Scoped slot instead ? V-for issue ?
Thanks for sharing your thoughts.