how to use "v-for" for adding or removing a row with multiple components - vue.js

i have a row with 3 components(in which is a defined component 1, component 2 and component 3, as showed in my previous question:
VueJs component undefined )
how can i add a row or remove a row (in which has 3 components) using v-for?
var data1={selected: null, items:["A", "B"]};
Vue.component('comp1', {
template: `<select v-model="selected">
<option disabled value="">Please select</option>
<option v-for="item in items" :value="item">{{item}}</option>
</select>`,
data:function(){
return data1
}
});
<!---similar for component 2 and 3--->
new Vue({
el: '#app',
data: {
rows:[]
},
methods:{
addRow: function(){
this.rows.push({});
},
removeRow: function(row){
//console.log(row);
this.rows.$remove(row);
}
},
});
in .html
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for ="row in rows">
<comp1></comp1>
<comp2></comp2>
<comp3></comp3>
<button #click="addRow">Add Row</button>
<button #click="removeRow(row)">Remove Row</button>
</div>
</div>

The code is pretty close. Try this.
console.clear()
const template = {
template: `<select v-model="selected">
<option disabled value="">Please select</option>
<option v-for="item in items" :value="item">{{item}}</option>
</select>`,
data: function() {
return {
selected: null,
items: ["A", "B"]
}
}
};
Vue.component("comp1", template)
Vue.component("comp2", template)
Vue.component("comp3", template)
new Vue({
el: '#app',
data: {
rows: []
},
computed:{
newId(){
return this.rows.length == 0 ? 1 : Math.max(...this.rows.map(r => r.id)) + 1
}
},
methods: {
addRow: function() {
this.rows.push({id: this.newId });
},
removeRow: function(row) {
this.rows.splice(this.rows.indexOf(row), 1)
}
},
});
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="row in rows" :key="row.id">
<comp1></comp1>
<comp2></comp2>
<comp3></comp3>
<button #click="removeRow(row)">Remove Row</button>
</div>
<button #click="addRow">Add Row</button>
</div>
This code moves the add row button outside the loop, because you don't really need multiple add row buttons. Additionally, it adds a key for each div in the loop so that Vue can properly remove components when necessary. In order to generate the key, the code creates an id property for each new row object.

Related

vue reactivity in list with slot scoped component

Good afternoon!
I have a loop and inside each element I display a separate component that takes props and gives them through the slot. I can't figure out why when new elements are added the update hooks happen on the old ones?
Example:
have: 1 2 3
add: 4
log: created 4, updated 3, updated 2, updated 1
Why is this happening?
Vue.component('slot-component', {
inheritAttrs: false,
created() {
console.log('created', this._uid);
},
updated() {
console.log('updated', this._uid);
},
render(h) {
return this.$scopedSlots.default(this.$attrs);
}
})
new Vue({
el: '#app',
data() {
return {
list: []
}
},
methods: {
add() {
this.list.push({
title: 'some'
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.8/vue.min.js"></script>
<div id="app">
<div class="list">
<div
v-for="(item, n) in list"
:key="n"
class="list-item"
>
<slot-component
:title="item.title"
v-slot="props"
>
{{ props.title }}
</slot-component>
</div>
<button #click="add">add</button>
</div>
</div>
Update
Thanks to Raphael Rollet for clarifying that all array elements are updated when the array is changed globally. But then it is not entirely clear why this behavior behaves differently with a component that uses props
Pass and use props — not call updated
Vue.component('slot-component', {
props: ['title'],
created() {
console.log('created', this._uid);
},
updated() {
console.log('updated', this._uid);
},
template: `<div>{{ title }}</div>`
})
new Vue({
el: '#app',
data() {
return {
list: []
}
},
methods: {
add() {
this.list.push({ title: 'some' });
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.8/vue.min.js"></script>
<div id="app">
<div class="list">
<slot-component
v-for="(item, n) in list"
:key="n"
:title="item.title"
class="list-item"
>
</slot-component>
<button #click="add">add</button>
</div>
</div>
Pass and use props with slot — call updated
Vue.component('slot-component', {
props: ['title'],
created() {
console.log('created', this._uid);
},
updated() {
console.log('updated', this._uid);
},
template: `<div><slot>{{ title }}</slot></div>`
})
new Vue({
el: '#app',
data() {
return {
list: []
}
},
methods: {
add() {
this.list.push({ title: 'some' });
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.8/vue.min.js"></script>
<div id="app">
<div class="list">
<slot-component
v-for="(item, n) in list"
:key="n"
:title="item.title"
class="list-item"
>
{{ item.title }}
</slot-component>
<button #click="add">add</button>
</div>
</div>
Use $attrs — call updated
Vue.component('slot-component', {
created() {
console.log('created', this._uid);
},
updated() {
console.log('updated', this._uid);
},
template: `<div>{{ $attrs.title }}</div>`
})
new Vue({
el: '#app',
data() {
return {
list: []
}
},
methods: {
add() {
this.list.push({ title: 'some' });
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.8/vue.min.js"></script>
<div id="app">
<div class="list">
<slot-component
v-for="(item, n) in list"
:key="n"
:title="item.title"
class="list-item"
>
</slot-component>
<button #click="add">add</button>
</div>
</div>
Use slot — call updated
Vue.component('slot-component', {
created() {
console.log('created', this._uid);
},
updated() {
console.log('updated', this._uid);
},
template: `<div><slot></slot></div>`
})
new Vue({
el: '#app',
data() {
return {
list: []
}
},
methods: {
add() {
this.list.push({ title: 'some' });
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.8/vue.min.js"></script>
<div id="app">
<div class="list">
<slot-component
v-for="(item, n) in list"
:key="n"
:title="item.title"
class="list-item"
>
<template #default></template>
</slot-component>
<button #click="add">add</button>
</div>
</div>
The behaviour who seems "weird" is all time because vue binding.
You'r array is update so all property inside is update, someone can have a nice explanation on that because it's too deep.
But the solution is not use a update function on your child (if you don't want update something on every child), but set a watcher on what you want and do something in that case.
Like if you update a title on a child you do that:
watch: {
title(value) {
console.log(value)
}
}
Update:
For the two last example in your update section, like i explain for me it's expected, it's seems weird yes, but it's binding with vue.
About the first, you use a props so you pass a string in your component and nothing else. He have no reason to update.
For the second you do the same thing but with a slot, the only explanation for me it's, when you initialise a slot (even empty) the slot access to the parent data, and because binding is update.
I found this:
https://vuejs.org/guide/components/slots.html#render-scope
Slot content has access to the data scope of the parent component,
because it is defined in the parent.
Be honest it's assumption, if anyone have a other explanation i want to know it !

Vue is not update DOM when object property changes in array of objects

I have a const array of objects which set to the data property on created(), I am trying to update the object properties as the user entered the form, the Console print shows the value updated, but the DOM is changing. DOM is updated when the key value is changed too but I do not want to change the key value
Data Array:
const invoices = [
{
number: "BBRFL",
client: "Ellison Daugherty",
amount: 8800,
status: "Paid",
},
{
number: "TYUBK",
client: "Myrna Vinson",
amount: 4097,
status: "Paid",
},
{
number: "IRUBR",
client: "Mcmillan Warner",
amount: 6466,
status: "Draft",
},
];
Here is the app.
const app = new Vue({
el: "#app",
components: {
"create-invoice": CreateInvoice,
"invoice-item": InvoiceItem,
},
data() {
return {
invoices: invoices,
status: null,
};
},
created: function () {
const invoices = localStorage.getItem("invoices");
if (invoices) {
this.invoices = JSON.parse(invoices);
}
},
computed: {
count: function () {
return this.filteredInvoices.length;
},
filteredInvoices: function () {
if (this.status) {
return this.invoices.filter((invoice) => invoice.status == this.status);
}
return this.invoices;
},
},
methods: {
saveInvoice(invoice) {
this.invoices.push(invoice);
},
invoiceUpdate(invoice) {
const index = this.invoices.findIndex((i) => i.number === invoice.number);
// this.invoices.splice(index, 1, invoice);
Vue.set(this.invoices[index], "client", invoice.client);
Vue.set(this.invoices[index], "amount", invoice.amount);
Vue.set(this.invoices[index], "status", invoice.status);
},
invoiceDelete(number) {
const index = this.invoices.findIndex((i) => i.number === number);
this.invoices.splice(index, 1);
},
},
watch: {
invoices: {
deep: true,
handler: function (invoices) {
localStorage.setItem("invoices", JSON.stringify(invoices));
},
},
},
template: `
<div>
<div class="row">
<div class="col-md-4 text-left">
<h1>Invoices</h1>
<p class="text-dark">There are {{count}} Total Invoices</p>
</div>
<div class="col-md-4 text-start" style="margin-top: 10px">
<label for="status">Filter</label>
<select name="filter" id="status" class="form-control" v-model="status">
<option value="Draft">Draft</option>
<option value="Paid">Paid</option>
<option value="Pending">Pending</option>
</select>
</div>
<div class="col-md-4 text-right" style="margin-top: 10px">
<button class="btn btn-primary mt-4" id="createInvoice">
New Invoice
</button>
</div>
</div>
<div class="col-md-12">
<div class="row mt-2 mb-2">
<div
class="invoice col-md-12"
>
<invoice-item
v-for="(n, index) in filteredInvoices"
:key="n.number"
:initial-client="n.client"
:initial-number="n.number"
:initial-amount="n.amount"
:initial-status="n.status"
#update-invoice="invoiceUpdate"
#delete-invoice="invoiceDelete"
></invoice-item>
</div>
<div class="text-center" v-if="filteredInvoices.length === 0"><p>No invoice found</p></div>
</div>
</div>
<create-invoice #saveInvoice="saveInvoice" ></create-invoice>
</div>
</div>
`,
});
I had tried, this.$set, Vue.set, Direct assigning to property, assingment using splice function, but none of working. It only works with the change of value of key in for loop. Which I do not want to update.
Jsfiddle
Any Help? Thanks in advance.

How to call a method function in watch vuejs

I write code like this:
<select class="section" #change="showIndex($event.target.selectedIndex)">
<option v-for="number in 50">{{ number}} per page</option>
</select>
<div class="product__layout" v-for="product in showItem">
<p class="product__name">{{ product.name }}</p>
...
data() {
return {
products:[
{ name: 'abc' },
{ name: 'xyz' },
],
}
},
methods:{
showIndex(selectedItem){
return selectedItem
}
},
computed:{
showItem(){
return this.products.slice(0, this.showItem)
}
}
My code doesn't have results. Can someone help me what i'm doing wrong?
It seems you want to show a subset of products based on the number from a combo box.
As mentioned in the comments, this is best done via a computed property
new Vue({
el: "#app",
data: () => ({
products:[
{ name: 'abc' },
{ name: 'xyz' },
],
productCount: 1
}),
computed: {
showProducts: ({ products, productCount }) => products.slice(0, productCount)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<p>
Count:
<!-- 👇 use v-model to capture the selection -->
<select v-model="productCount" class="section">
<option v-for="number in 50" :value="number">{{ number }} per page</option>
</select>
</p>
<!-- 👇 use the computed property here -->
<div v-for="product in showProducts" class="product__layout" :key="product.name">
<p class="product__name">{{ product.name }}</p>
</div>
</div>

Apply v-focus to the first input field on a page

I've a Vue component in which I'm trying to autofocus the first field using v-focus. But my problem is, I've dynamic components that will be included at the top of the page. So in that case how can I apply autofocus to dynamically included component?
They key is to set ref on all your inputs to the same string like this:
<input type="text" ref="myInputs"/>
Then you will have access to an array called this.$refs.myInputs inside an event handler.
So you just need to do
this.$refs.myInputs[0].focus();
new Vue({
el: "#app",
mounted() {
this.$refs.myInputs[0].focus();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<div>
<div v-for="index in 3" :key="index">
<input ref="myInputs" type="text" />
</div>
</div>
</div>
It's hard to tell how you're adding the input(s) to the DOM, without any pseudo code from you, but this is one way to do it..
[CodePen mirror]
new Vue({
el: "#app",
data: {
inputs: ["firstName", "lastName"]
},
watch: {
inputs() {
this.$nextTick(() => {
this.focusFirstInput();
});
}
},
methods: {
focusFirstInput() {
let first = this.inputs[0];
let firstInput = this.$refs[first][0];
firstInput.focus();
},
handleClick() {
this.inputs.push("newInput");
}
},
mounted() {
this.focusFirstInput();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<div>
<div v-for="(input, index) in inputs" :key="index">
<input :ref="input" type="text" />
</div>
<div>
<button type="button" #click="handleClick">Click to add input</button>
</div>
</div>
</div>
I found this answer on Laracast and it worked for me. All I did was insert the code below in my dynamic form field.
this.$nextTick(() => {
let index = this.items.length - 1;
let input = this.$refs.title[index];
input.focus();
});
HTML
<div id="app">
<ul v-for="item in items">
<li>
<input :ref="'title'" v-model="item.title">
</li>
</ul>
<button v-on:click="addItem">Add Item</button>
</div>
JS
let app = new Vue({
el: '#app',
data: {
items: [
{title: 'Apple'},
{title: 'Orange'},
]
},
methods: {
addItem(){
this.items.push({title: "Pineapple"});
this.$nextTick(() => {
let index = this.items.length - 1;
let input = this.$refs.title[index];
input.focus();
});
}
}
});
Note: make sure to add :ref="'title'" into your dynamic form field.
Credits to the original author of the solution.

issue caused the Row selection overwrite

I asked question on adding/removing row in how to use "v-for" for adding or removing a row with multiple components
However, I got a bug: when I adding a row, the items in the first row filled to second row and when I changed the second row, the 1st row is also overwritten as the same as 2nd row.
i must did sth really wrong.
in .js
var data1={selected: null, items: ["A1","B1"]};
Vue.component('comp1',{
template: ` <select v-model="selected">
<option disabled value="">Please select</option>
<option v-for="item in items" :value="item">{{item}}
</option>
</select>`,
data:function(){
return data1
}
});
var data2={selected: null, items: ["A2","B2"]};
Vue.component('comp2',{
template: ` <select v-model="selected">
<option disabled value="">Please select</option>
<option v-for="item in items" :value="item">{{item}}
</option>
</select>`,
data:function(){
return data2
}
});
new Vue({
el: '#app',
data: {
rows: []
},
computed:{
newId(){
return this.rows.length == 0 ? 1 : Math.max(...this.rows.map(r => r.id)) + 1
}
},
methods: {
addRow: function() {
this.rows.push({id: this.newId });
},
removeRow: function(row) {
this.rows.splice(this.rows.indexOf(row), 1)
}
},
});
in .html
<div id="app">
<div v-for="row in rows">
<comp1></comp1>
<comp2></comp2>
<button #click="removeRow(row)">Remove Row</button>
</div>
<button #click="addRow">Add Row</button>
</div>
You need to add the key.
<div v-for="row in rows" :key="row.id">
And you shouldn't share the data between the components, so move your data into the components.
data: function() {
return {
selected: null,
items: ["A1", "B1"]
}
}
Here is a working version.
Vue.component('comp1', {
template: ` <select v-model="selected">
<option disabled value="">Please select</option>
<option v-for="item in items" :value="item">{{item}}
</option>
</select>`,
data: function() {
return {
selected: null,
items: ["A1", "B1"]
}
}
});
Vue.component('comp2', {
template: ` <select v-model="selected">
<option disabled value="">Please select</option>
<option v-for="item in items" :value="item">{{item}}
</option>
</select>`,
data: function() {
return {
selected: null,
items: ["A2", "B2"]
}
}
});
new Vue({
el: '#app',
data: {
rows: []
},
computed: {
newId() {
return this.rows.length == 0 ? 1 : Math.max(...this.rows.map(r => r.id)) + 1
}
},
methods: {
addRow: function() {
this.rows.push({
id: this.newId
});
},
removeRow: function(row) {
this.rows.splice(this.rows.indexOf(row), 1)
}
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<div v-for="row in rows" :key="row.id">
<comp1></comp1>
<comp2></comp2>
<button #click="removeRow(row)">Remove Row</button>
</div>
<button #click="addRow">Add Row</button>
</div>
Specifically, the way you are sharing data is because you are defining the data like this:
var data1={selected: null, items: ["A1","B1"]};
And returning that object from your data function in the component:
data:function(){
return data1
}
This means that every instance of that component is sharing the same data. That's not what you want with components. Each component should have it's own copy of the data. In this case, there is no need whatsoever to define the data object returned from the data function outside the component.