Vue.js find which component emitted an event - javascript

I'm trying to have a component representing a shopping item.
I'll have one of this component for every item in my shopping list.
I don't know how to update the parent data (the shopping list) when the child is edited (the shopping item)
Shopping List
<template>
<div id="app">
<shopping-item
v-for="(item, index) in shoppingList"
:key="index"
:propsName="item.name"
:propsQuantity="item.quantity"
#shoppingItemEdited="handleEdit"
></shopping-item>
</div>
</template>
<script>
import ShoppingItem from "./components/ShoppingItem.vue";
export default {
name: "App",
components: {
ShoppingItem,
},
data() {
return {
shoppingList: [
{ name: "apple", quantity: 8 },
{ name: "banana", quantity: 3 },
{ name: "kiwi", quantity: 7 },
{ name: "peach", quantity: 5 },
],
};
},
methods: {
handleEdit(itemEdited) {
// How to get the index of the shopping-item that has been updated ?
// shoppingList[???] = itemEdited
console.log(itemEdited);
// => {name: "white peach", quantity: "6"}
},
},
};
</script>
Shopping Item
<template>
<div>
<input v-model="name" placeholder="ex: banana" #change="updateParent" />
<input
v-model="quantity"
type="number"
placeholder="ex: 3"
#change="updateParent"
/>
</div>
</template>
<script>
export default {
data() {
return {
name: "",
quantity: null,
};
},
props: {
propsName: String,
propsQuantity: Number,
},
created() {
this.name = this.propsName;
this.quantity = this.propsQuantity;
},
methods: {
updateParent() {
this.$emit("shoppingItemEdited", {
name: this.name,
quantity: this.quantity,
});
},
},
};
</script>
So I have few questions:
How can I know witch component emited the event 'shoppingItemEdited' ? If I knew it, I could find out which shoppingList item I should update.
I red I should not update props in the child, so I create data based on props, is that a standard way of doing that ?
this.name = this.propsName;
this.quantity = this.propsQuantity;

Just pass an index to a handler: #shoppingItemEdited="handleEdit(index, $event)"
No it's not "standard" - created hook is called only once when component is created, so if value of prop changes later (from parent), data will not update. It's probably not a problem in your case but usually its better to use computed:
computed: {
name: {
get() { return this.propsName },
set(value) {
this.$emit("shoppingItemEdited", {
name: value,
quantity: this.quantity,
});
}
}
}
...handle event in parent and the change will propagate (by props) to a child

Related

How to iterate array value by comparing it with another array in Vuejs?

HelloWorld.vue
<template>
<div>
<div v-for="box in boxes" :key="box.sname">
<BaseAccordian>
<template v-slot:title>{{ box.sname }}</template>
<template v-slot:content>
<div v-for="paint in paints" :key="paint.tname" class="line">
<List :content="matchingdata" :sname="box.sname" />
</div>
</template>
</BaseAccordian>
</div>
</div>
</template>
<script>
import BaseAccordian from "./BaseAccordian.vue";
import List from "./List.vue";
export default {
name: "HelloWorld",
components: {
BaseAccordian,
List,
},
data() {
return {
boxes: [
{
sname: "apple",
},
{
sname: "bananna",
},
{
sname: "grapes",
},
{
sname: "choc",
},
],
paints: [
{
tname: "a",
},
{
tname: "b",
},
{
tname: "c",
},
{
tname: "d",
},
{
tname: "e",
},
],
matchingdata: [
{
matchid: "1",
OverallStatus: "ok",
sname: "choc",
},
{
matchid: "2",
OverallStatus: "notok",
sname: "grapes",
},
],
};
},
};
</script>
BaseAccordion.vue
<template>
<div class="wrapper">
<div class="accordion">
<input type="checkbox" #click="toggleItem" />
<h2 class="title">
<slot name="title"></slot>
</h2>
</div>
<div v-show="show" class="content">
<slot name="content"></slot>
</div>
</div>
</template>
<script>
export default {
components: {},
data: function () {
return {
show: false,
};
},
methods: {
toggleItem: function () {
this.show = !this.show;
},
},
};
</script>
List.vue
<template>
<div class="">
<div
v-for="match in matchingData"
:key="match.matchid"
:class="{
green: match.OverallStatus === 'ok',
red: match.OverallStatus === 'notok',
}"
>
{{ match.OverallStatus }}
</div>
</div>
</template>
<script>
export default {
components: {},
props: {
content: {
type: Array,
required: true,
},
sname: {
type: String,
required: true,
},
},
data: function () {
return {};
},
methods: {},
computed: {
matchingData() {
return this.content.filter((a) => {
if (a.sname === this.sname) {
return true;
} else {
return false;
}
});
},
},
};
</script>
<style scoped>
</style>
I three arrays called matchingdata,boxes,paints array based on this three arrays, i am trying to filter the array.(nested v-for)
Now, I want to iterate the matchingdata array by comparing it with sname in boxes array. and Common value between matchingdata and boxes array is ""sname""
I tried above logic, and struck with computed property.
Expected Output:-
In List.vue component , i have
{{ match.OverallStatus }} where that field , i want to show,(from the matchingdata array) when user clicked on checkbox.
Taking the ""sname"" the common value from the matchingdata array and the boxes array
code:- https://codesandbox.io/s/damp-pine-27s2kn?file=/src/components/List.vue
As you're passing the sname property as a string via a prop to your List.vue component, you'll just need to use that string in your filter function.
matchingData() {
return this.content.filter((a) => a.sname === this.sname)
},
I've tried this in your codesandbox link and it is giving some output - but I'm not clear enough on what you're trying to achieve to know if this is the intended outcome.
Just incase you're not aware the 'filter' function returns a new array. It's not going to return a 'true/false' which I feel you may be trying to do.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

How to make sure all radiobox questions have been interacted with

A small app where user needs to select either Yes or No for each task. The user should NOT be allowed to leave the page unless they have answered all questions. I have added a button at the bottom of the form which simulates beforeRouterLeave behaviour.
My question is about the simulateBeforeRouteLeave() function. How can do I check if all questions have been answered to allow user to leave?
Ps. This is a snipped from a much larger project. The original data comes from a API and its NOT hard coded like in this example.
CodeSandbox
App.vue
<template>
<parent-component
v-for="task in tasks.data"
:key="task.id"
:taskData="task"
#update-form-data="handleChange"
>
</parent-component>
<button
class="my-2 border-2 border-red-500"
#click="simulateBeforeRouteLeave"
>
simulateBeforeRouteLeave
</button>
</template>
<script>
import parentComponent from "./components/parentComponent.vue";
export default {
components: {
parentComponent,
},
methods: {
simulateBeforeRouteLeave() {
//Need HELP Here! How do I write this IF statement?
if ("Options_NOT_Selected") {
alert("Please select all options");
} else {
console.log("Allow user to leave 'next()'");
}
},
handleChange(e) {
const objIndex = this.tasks.data.findIndex((obj) => obj.id === e.id);
this.tasks.data[objIndex].status = e.status;
},
},
data() {
return {
markAll: false,
tasks: {
data: [
{
id: 1,
name: "Task 1",
status: null,
},
{
id: 2,
name: "Task 2",
status: null,
},
{
id: 3,
name: "Task 3",
status: null,
},
],
},
};
},
};
</script>
parentComponent.vue
<template>
<div class="py-2">
<div>
<input
type="radio"
:id="taskData.id + 'Yes'"
:name="taskData.name"
:value="taskData.value"
#change="updateData"
/>
<label :for="taskData.id">{{ taskData.name }} Yes</label>
</div>
<div>
<input
type="radio"
:id="taskData.id + 'No'"
:name="taskData.name"
:value="taskData.value"
/>
<label :for="taskData.id">{{ taskData.name }} No</label>
</div>
</div>
</template>
<script>
export default {
props: ["taskData"],
methods: {
updateData(e) {
this.$emit("update-form-data", {
id: this.taskData.id,
status: 1,
});
},
},
data() {
return {};
},
};
</script>

How do I display one object of an array in Vue.JS

Let's say I have this list of objects in Vue.JS
data () {
return{
examples: [
{
exampleID: 5,
exampleText: 'foo'
},
{
exampleID: 3,
exampleText: 'bar'
}
]
}
}
Now let's say I want to display the object with the exampleID of 3 in an element i created before
<Task
v-for="example in examples"
:key="example.exampleID"
:example="example"
/>
I want to display everything, that is in the object (the ID and the text)
Task component :
<template>
<div class="exercise">
<div class="exercise-id">
<h1>ID NUMBER: {{ example.exampleID }}</h1>
</div>
<div class="exercise-task">
<h2>{{ example.exampleText}}</h2>
</div>
</div>
</template>
<script>
export default {
name: 'Task',
props: ['example']
}
</script>
You shouldn't use v-if and v-for in the same element, in this case i suggest to use a computed property that only return the desired example :
data () {
return{
examples: [
{
exampleID: 5,
exampleText: 'foo'
},
{
exampleID: 3,
exampleText: 'bar'
}
]
}
},
computed:{
myExample(){
return this.examples.find(ex=>ex.exampleID===3)
}
}
then render it like :
<Task :example="myExample"/>
Another efficient way of doing it without v-for is
data () {
return{
examples: [
{
exampleID: 5,
exampleText: 'foo'
},
{
exampleID: 3,
exampleText: 'bar'
}
],
id: 3
}
},
computed:{
myExample(){
return id => this.examples.find(ex=>ex.exampleID===id)
}
}
rendering part will be like
<Task :example="myExample(id)"/>
In this way you no need to hardcode the value as 3 in the computed property.

Updating State Through props with hooks

I am trying to update the state of something on a click from a component by lifting up the state and passing it as a prop into the other component and trying to update it.
this is the App.js
function App() {
const [currentConfig, setCurrentConfig] = useState(0);
const availableConfigs = [
{ id: 1, name: "Config 1", number: 1, key: 1 },
{ id: 2, name: "Config 2", number: 2, key: 2 },
{ id: 3, name: "Config 3", key: 3 },
{ id: 4, name: "Config 4", key: 4 },
{ id: 5, name: "Config 5", key: 5 },
{ id: 6, name: "Config 6", key: 6 },
{ id: 7, name: "Config 7", key: 7 },
];
const [configs, setConfigs] = useState(availableConfigs);
//function undoConfigAnimation(currentConfig) {}
return (
<div>
<Tree
configs={configs}
animateConfigs={startConfigAnimation}
setConfig={setCurrentConfig}
currentConfig={currentConfig}
/>
<NavBar />
</div>
);
function startConfigAnimation(configClicked) {
console.log(currentConfig);
configs.forEach((config) => {
if (configClicked !== config.name) {
var elm = document.getElementById(config.name);
elm.style.transform = "translate(-200px)";
setTimeout(() => (elm.style.transform = "rotateZ(180deg)"), 1000);
}
});
}
}
export default App;
this is the component
function Tree(props) {
return (
<div class="treeContainer">
{props.configs.map((config) => {
return (
<div
id={config.name}
class="container1"
onClick={() => {
props.setConfig(config.name);
props.animateConfigs(config.name);
if (props.currentConfig !== config.name) {
props.setConfig.bind(config.name);
}
}}
>
<Configs configNumber={config.number} configName={config.name} />
</div>
);
})}
</div>
);
}
export default Tree;
currently, it does update the state, but it only updates it to the state before the click so an example output if the currentConfig === 0 would be as follows
click config 1
currentConfig = 0
click config 2
currentConfig = "config 1"
Since the setState is async, the console.log will always be one behind. This does not mean that the state is not updated, but only not displayed in the console or yet available in the function.
So the flow would be:
You dispatch the change.
You call startConfigAnimation, but it is still in sync, so that currentConfig is still the previous value.
The state is updated with the new value.
There are 2 ways to fix this:
Use a useEffect:
Listen to the currentConfig with a useEffect and trigger the animation, if the config changes.
React.useEffect(() => startConfigAnimation(currentConfig), [currentConfig])
You are already passing the new/updated config to startConfigAnimation so you could be using that.

Accessing the computed properties of components in Vue from the parent

How do you access the computed properties of components in Vue from the parent?
In this example, I have a cart with item components and I want to compute and display the sum of the cart items:
cart.js
var vm = new Vue({
el: '#cart',
components: {
cartItem: require('./components/cart-item.js'),
},
data: {
items: [
{ name: 'apple', qty: 5, price: 5.00 },
{ name: 'orange', qty: 7, price; 6.00 },
],
},
computed: {
// I want to do something like this and access lineTotal from cart
cartTotal: function() {
return this.items.reduce(function(prev,curr) {
return prev + curr.lineTotal;
}, 0)
}
}
});
cart-item.js
module.exports = {
template: require('./cart-item.template.html'),
props: ['fruit'],
computed: {
lineTotal: function() {
return this.fruit.price * this.fruit.qty;
}
},
};
main html
<li v-for="item in items" is="cart-item" :fruit="item">
...
#{{ cartTotal }}
How do I access the lineTotal properties of each cart-item to use in summing cartTotal?
Note that I do not want to redo the calculations done in lineTotal but instead use the computed properties directly.
You have to name the children, for example by means of the v-ref directive. Then from the parent you resolve the children properties with this.$refs.mychild.myproperty

Categories