How to call a method function in watch vuejs - vue.js

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>

Related

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 can I access the bound value of a <select>'s currently selected <option>?

Say I am making a simple component which wraps a <select>. This component supports v-model, as documented here.
Vue.component('custom-select', {
template: '#component',
props: ['options', 'value'],
});
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<script type="text/x-template" id="component">
<div id="component">
<select :value="value" #input="$emit('input', $event.target.value)">
<option v-for='option in options' :value="option">
<slot v-bind="{ option }"></slot>
</option>
</select>
</div>
</script>
This works fine if the options are strings. However, if they are a different type (e.g. objects), then the values emitted are cast to strings (e.g. '[object Object]'). This is because $event.target.value pulls the value from the DOM, which will always be a string type.
Is there a way to get the original bound value of the selected <option>? I'm aware of v-model as an option, but it complicates things as it requires adding watchers.
EDIT I have discovered that Vue seems to assign the original bound value to the _value property on the DOM node, though I'm not sure if accessing that is a good idea since it's underscore prefixed and seems to be undocumented.
Let's say options prop is an array of object as below
You can change the event emitter of child component to return object instead of string like this:
<style>
[v-cloak] {
display: none;
}
</style>
<!-- // App -->
<div id="app">
<div v-cloak>
Value in parent: {{selectedValue}}
<br><br>
<custom-select :options='selectOptions' v-model='selectedValue'></custom-select>
</div>
</div>
<!-- // JS Code -->
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<script type="text/x-template" id="component">
<div id="component">
<select #change="$emit('input', options.find(option => option.value == $event.target.value))">
<option v-for='option in options' :value="option.value">
{{ option.text }}
</option>
</select>
</div>
</script>
<script>
// Mount App
new Vue({
el: '#app',
data() {
return {
selectOptions: [
{ text: 'Apple', value: 'apple', price: '10' },
{ text: 'Banana', value: 'banana', price: '20' },
{ text: 'Strawberry', value: 'strawberry', price: '30' },
],
selectedValue: {}
}
},
// Custom component
components: {
'custom-select': Vue.component('custom-select', {
template: '#component',
props: ['options', 'value'],
})
}
})
</script>
While I still haven't found an exact solution to my original question, I've found a design pattern that I think solves the issue satisfactorily. By using a computed property with a getter and setter, I can use v-model on the <select> without needing watchers or any internal component state.
Vue.component('custom-select', {
template: '#component',
props: ['options', 'value'],
computed: {
valueProxy: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', newValue);
},
},
},
});
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<script type="text/x-template" id="component">
<div id="component">
<select v-model="valueProxy">
<option v-for='option in options' :value="option">
<slot v-bind="{ option }"></slot>
</option>
</select>
</div>
</script>

Getting a single array from an array of object

I am trying to iterate through the array of object called sources at change event of my select input then pushing this single array to a new element conditionally
<template>
<div class="sourceselection">
<div class="jumbotron">
<h2><span class="glyphicon glyphicon-list"></span> News List</h2>
<h4>Select News Source</h4>
<select class="form-control" #change="sourceChanged">
<option v-bind:value="source.id" v-for="source in sources" :key="source.id">{{source.name}}</option>
</select>
</div>
<div v-if="source">
<h6 >{{source.description}}</h6>
<a v-bind:href="source.url"></a>
</div>
</div>
</template>
<script>
export default {
name: 'SourceSelection',
data () {
return {
sources: [],
source: '',
}
},
methods: {
sourceChanged (event) {
for (var i = 0; i < this.sources.length; i++){
if (this.sources[i].id == event.target.value){
this.source = this.sources[i];
}
}
}
},
created : function () {
this.$http.get('https://newsapi.org/v2/sources?language=en&apiKey=cf0866b5aaef42e995f9db37bb3f7ef4')
.then(response => {
this.sources = response.data.sources;
})
.catch(error => console.log(error));
}
}
</script>
i expect to pull the description into my h6 if the source exist
At first create a state in data and name it sourceId,
then bind it to select <select v-model="sourceId" class="form-control">.
Now we need a computed filed which returns the source return this.sources.find(s => s.id === this.sourceId)
<template>
<div class="sourceselection">
<div class="jumbotron">
<h2><span class="glyphicon glyphicon-list"></span> News List</h2>
<h4>Select News Source</h4>
<select v-model="sourceId" class="form-control">
<option v-for="source in sources" :value="source.id" :key="source.id">
{{ source.name }}
</option>
</select>
</div>
<div v-if="source">
<h6 >{{ source.description }}</h6>
<a v-bind:href="source.url"></a>
</div>
</div>
</template>
<script>
export default {
name: 'SourceSelection',
data () {
return {
sources: [],
sourceId: ''
}
},
computed: {
source () {
return this.sources.find(s => s.id === this.sourceId)
}
},
created() {
this.$http.get('https://newsapi.org/v2/sources?language=en&apiKey=cf0866b5aaef42e995f9db37bb3f7ef4')
.then(response => {
this.sources = response.data.sources;
})
.catch(error => console.log(error));
}
}
</script>
Instead of using an event handler you can bind the selected value directly to your source data.
here is a demo:
Vue.config.devtools = false
Vue.config.productionTip = false
new Vue({
el: '#app',
data() {
return {
sources: [{
id: '1',
description: 'one desc',
name: 'one'
}, {
id: '2',
description: 'two desc',
name: 'two'
}, ],
source: ''
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<select class="form-control" v-model="source">
<option v-bind:value="source" v-for="source in sources" :key="source.id">{{source.name}}</option>
</select>
<h6>{{ source.description }}</h6>
</div>

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.

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

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.