Goal
I want to detect only a change event to searchTerms.
Problem
The watcher currently triggers on each keypress, but I don't want that many events.
Context (View Fiddle)
<template>
<div id="app">
<table class="table">
<tr>
<td><label>Name</label></td>
<td><input class="form-control" v-model="customer.name" autofocus></td>
</tr>
<tr>
<td><label>Short Code</label></td>
<td><input class="form-control" v-model="customer.shortCode"></td>
</tr>
<tr>
<td><label>Address</label></td>
<td><input class="form-control" v-model="customer.address"></td>
</tr>
<tr>
<td><label>Caller</label></td>
<td><input class="form-control" v-model="customer.caller"></td>
</tr>
<tr>
<td><label>Phone</label></td>
<td><input class="form-control" v-model="customer.phone"></td>
</tr>
</table>
<div class="models">
<pre><strong>customer:</strong> {{ customer | json }}</pre>
<pre><strong>searchTerms:</strong> {{ searchTerms | json }}</pre>
</div>
</div>
</template>
<script>
new Vue({
el: '#app',
data: {
customer: {
name: 'Donnie',
phone: '',
caller: '',
address: '',
shortCode: 'DO'
}
},
computed: {
searchTerms: function() {
let terms = {};
_.forOwn(this.customer, (value, key) => {
if (value.length >= 3) {
terms[key] = value;
}
});
return terms;
}
},
watch: {
'searchTerms': function() {
if (_.isEmpty(this.searchTerms)) {
return;
}
alert('searchTerms Changed');
}
}
});
</script>
The computed property searchTerms creates a new object every time it runs. This means that the reference to searchTerms changes, causing the watcher to fire.
You only want the watcher to fire if one of the values has changed. The easiest way to do this is to watch a stringified version of searchTerms, rather than the object.
Here is the updated fiddle: https://jsfiddle.net/qLzu0seq/5/
And here is the code as a snippet (it's nice to keep code in stackoverflow, rather than an external site):
new Vue({
el: '#app',
data: {
customer: {
name: 'Donnie',
phone: '',
caller: '',
address: '',
shortCode: 'DO'
}
},
computed: {
searchTerms: function() {
let terms = {};
_.forOwn(this.customer, (value, key) => {
if (value.length >= 3) {
terms[key] = value;
}
});
return terms;
},
searchTermsStringified: function() {
return JSON.stringify(this.searchTerms);
}
},
watch: {
'searchTermsStringified': function() {
if (_.isEmpty(this.searchTerms)) {
return;
}
alert('searchTerms Changed');
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.4/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.js"></script>
<div id="app">
<table class="table">
<tr>
<td><label>Name</label></td>
<td><input class="form-control" v-model="customer.name" autofocus></td>
</tr>
<tr>
<td><label>Short Code</label></td>
<td><input class="form-control" v-model="customer.shortCode"></td>
</tr>
<tr>
<td><label>Address</label></td>
<td><input class="form-control" v-model="customer.address"></td>
</tr>
<tr>
<td><label>Caller</label></td>
<td><input class="form-control" v-model="customer.caller"></td>
</tr>
<tr>
<td><label>Phone</label></td>
<td><input class="form-control" v-model="customer.phone"></td>
</tr>
</table>
<div class="models">
<pre><strong>customer:</strong> {{ JSON.stringify(customer,null,2) }}</pre>
<pre><strong>searchTerms:</strong> {{ JSON.stringify(searchTerms,null,2) }}</pre>
</div>
</div>
You can check if the value has changed directly in the computed property function.
Since you are generating objects you need to use _.isEqual method in order to test if the value has changed. You will also need to store the previous value for comparison.
new Vue({
el: '#app',
data: {
customer: {
name: 'Donnie',
phone: '',
caller: '',
address: '',
shortCode: 'DO'
},
previousSearchTerms: null
},
computed: {
searchTerms: function() {
let terms = {};
_.forOwn(this.customer, (value, key) => {
if (value.length >= 3) {
terms[key] = value;
}
});
if (this.previousSearchTerms && !_.isEqual(terms, this.previousSearchTerms)) {
alert('I was changed !');
}
this.previousSearchTerms = terms;
return terms;
}
}
});
label { font-weight: bold; }
.models {
background: #eee;
margin: 1rem;
padding: 1rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.5/css/bootstrap.min.css" />
<div id="app">
<table class="table">
<tr>
<td><label>Name</label></td>
<td><input class="form-control" v-model="customer.name" autofocus></td>
</tr>
<tr>
<td><label>Short Code</label></td>
<td><input class="form-control" v-model="customer.shortCode"></td>
</tr>
<tr>
<td><label>Address</label></td>
<td><input class="form-control" v-model="customer.address"></td>
</tr>
<tr>
<td><label>Caller</label></td>
<td><input class="form-control" v-model="customer.caller"></td>
</tr>
<tr>
<td><label>Phone</label></td>
<td><input class="form-control" v-model="customer.phone"></td>
</tr>
</table>
<div class="models">
<pre><strong>customer:</strong> {{ customer | json }}</pre>
<pre><strong>searchTerms:</strong> {{ searchTerms | json }}</pre>
</div>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.12/vue.js"></script>
You can use debounce for this, which is provided by lodash. It Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked. Debouncing is used to limit how often we execute Ajax requests and other expensive operations
You can add the things you want to not call that often in a separate method, and call those actions inside a _.debounce, like following:
methods: {
// This is where the debounce actually belongs.
expensiveOperation: _.debounce(function () {
this.isCalculating = true
setTimeout(function () {
alert('searchTerms Changed');
}.bind(this), 1000)
}, 500)
}
You can change the delay in setTimeout as par your requirement.
Find updated fiddle here.
Related
I would like to filter data in table using checkbox and outoput data will be shown based on checked/unchecked, for example when i'm checked "Show All" will ouput all data, and when i'm unchecked then output only will show some data. but when i'm trying to do this, the result nothing changed when i'm checked
here for vue html:
<div class="col-md-8">
<input type="checkbox" id="checkboxOrder" v-model="checkedValue" value="Onloading Complete" >
<label for="checkboxOrder">Show All</label>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>code</th>
<th>price</th>
<th>status</th>
<th>transporter</th>
<th>driver</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in showOrderDetail" :key="index">
<td >{{Number(index)+1}}</td>
<td>{{item.code}}</td>
<td>{{formatNumber(item.invoice_price)}}</td>
<td :class="statusBackground(item)">
<template v-if="item.t_tour">
<span v-if="!_.isEmpty(item.t_tour.t_tour_detail)">
{{item.t_tour.t_tour_detail[0].status}}
</span>
<span v-else>Unproccess</span>
</template>
<span v-else>Unproccess</span>
</td>
<td class="bg-warning">{{item.m_transporter ? item.m_transporter.name : 'unproccess'}}</td>
<td>{{driver(item)}}</td>
<td><span class="btn btn-primary" #click="showModal(item.t_tour)"><i class="fas fa-search"></i></span></td>
</tr>
</tbody>
</table>
and my vue.js :
import axios from "axios";
import accounting from "accounting";
import ShowTourDetail from "~/pages/transaction/order/ShowTourDetail";
export default {
props: {
rowData: {
type: Object,
required: true
},
rowIndex: {
type: Number
}
},
data() {
return {
t_order_detail: {},
checkedValue:[]
};
},
mounted() {
this.fetchData();
},
computed:{
showOrderDetail(){
if(!this.checkedValue.length)
return this.t_order_detail
return this.t_order_detail.filter(o => this.checkedValue.includes(o.t_tour.t_tour_detail[0].status))
}
},
methods: {
async fetchData() {
this.t_order_detail = {
...(
await axios.get(`/api/order-details`, {
params: { t_order_id: this.rowData.id }
})
).data
};
}
};
You can apply change method like :
<input type="checkbox" :value="mainCat.merchantId" id="mainCat.merchantId" v-model="checkedCategories" #change="check($event)">
Methos like:
methods: {
check: function(e) {
if(e.target.checked){
console.log("load all data using service");
// service call here
}else{
console.log("load required data using service");
// service call here
}
}
}
I have a HTML table in which in one component I have table head and other stuff and in other component I have tbody so in every row of tbody I am adding a delete row button which I want to delete but the data is in theadcomponent there is no parent-child relation here so how can I do this.
My code
Vue.component("form-row", {
template: "#row-template",
props: {
itemname: String,
quantity: Number,
sellingprice: Number,
amount: Number
},
methods: {
delete() {
alert("tedt")
}
},
computed: {
quantitySynced: {
get() {
return this.quantity;
},
set(v) {
this.$emit("update:quantity", +v);
}
},
sellingpriceSynced: {
get() {
return this.sellingprice;
},
set(v) {
this.$emit("update:sellingprice", +v);
}
},
amountSynced() {
this.$emit("update:amount", parseFloat(this.quantity) * parseFloat(this.sellingprice));
return this.amount
}
}
});
new Vue({
el: "#app",
data() {
return {
tableDatas: []
};
},
methods: {
btnOnClick(v) {
this.tableDatas.push({
itemname: "item",
quantity: 1,
sellingprice: 55,
amount: 55
});
}
},
computed: {
calculate() {
return (
this.tableDatas.reduce((total, {
amount
}) => total + amount, 0) || 0
);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button type="button" #click="btnOnClick">Add</button>
<table class="table table-striped table-hover table-bordered mainTable" id="Table">
<thead>
<tr>
<th class="itemName">Item Name</th>
<th>Quantity</th>
<th>Selling Price</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<form-row v-for="(row, key) in tableDatas" :key="key" v-bind.sync="row"></form-row>
</tbody>
</table>
<div>
<label>Total Row's Amount</label>
<input type="text" disabled :value="calculate">
</div>
</div>
<script type="text/x-template" id="row-template">
<tr>
<td>
<input class="form-control" readonly :value="itemname" />
</td>
<td>
<input class="form-control text-right" type="number" min="0" step="1" v-model="quantitySynced" />
</td>
<td>
<input class="form-control text-right" type="number" min="0" step=".5" v-model="sellingpriceSynced" />
</td>
<td>
<input readonly class="form-control text-right" type="number" min="0" step="1" :value="amountSynced" />
</td>
<td>
<button>Delete</button>
</td>
</tr>
</script>
Here I have two components In one component delete button is the I know how to delete the row in same component by using this:
delete(index) {
console.log("Removing", index);
this.tableDatas.splice(index, 1);
}
Vue Mixins will work for your problem.
Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.
Here is an example;
// define a mixin object
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// define a component that uses this mixin
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
Use the Shared events to catch between parent to component
var sharedEvents = new Vue();---declare on top of the vue instance
//parent
new Vue({
el: "#app",
---code--
methods:
getData(){
//emit the data in parent like this
sharedEvents.$emit('forceInjectGridData', {id: "billing-grid", data: data});
}
});
And catch the data in component like below wherever you want:
methods:{
forceInjectGridData(v) {
if (this.id === v.id) {
this.gridData = v.data;
//do your coding here
}
},
} ,
mounted(){
sharedEvents.$on('forceInjectGridData', this.forceInjectGridData);
}
Hi guys I have the following code where I look over a json file and add the data to a table.
I want the data from product.refrencing_category_ids to be output in on separate lines instead being in one line like this:
bc-men,bc-men-fashion,bc-men-underwear
I would like it to look like:
bc-men,
bc-men-fashion,
bc-men-underwear
How would i go about doing that? Would I need another for loop for the product.refrencing_category_ids?
My code look like this:
<template>
<div>
<h1>Category Assignment</h1>
<table class="table">
<tr class="table-header">
<th>ID</th>
<th>Name</th>
<th>Primary category</th>
<th>Refrencing categories</th>
<th>Add</th>
<tr>
<tr class="product" v-for="product in products">
<td class="product__item"><input required type="text" v-model="product.id"></td>
<td class="product__item"><input required type="text" name="fname" v-model="product.name"></td>
<td class="product__item">
<input required type="text" name="fname" v-model="product.primary_category_id">
</td>
<td class="product__item">
<input required type="text" name="fname" v-model="product.refrencing_category_ids">
</td>
<td class="product__item">
<button v-on:click="product.quantity += 1">
Add
</button>
</td>
</tr>
</table>
<h2>Total inventory: {{ totalProducts }}</h2>
</div>
</template>
<script>
export default {
name: 'ProductEnrichment',
data () {
return {
products: [],
productHeadline: 'Product Flow Tool'
}
},
computed: {
totalProducts () {
return this.products.reduce((sum, product) => {
return sum + product.quantity
}, 0)
}
},
created () {
fetch('https://www.fennefoss.dk/product-request.json')
//fetch('./sample.json')
.then(response => response.json())
.then(json => {
this.products = json.products
})
}
}
</script>
Yes to another loop with v-for. To make the list item reactive, you need to update the original data and not the computed one. Something like this:
new Vue({
el: '#app',
data() {
return {
product: {
refrencing_category_ids: 'bc-men,bc-men-fashion,bc-men-underwear'
}
}
},
methods: {
remove(idx) {
let items = this.product.refrencing_category_ids.split(',');
let removed = items.splice(idx, 1);
this.product.refrencing_category_ids = items.join(',');
}
},
computed: {
refCatIds() {
return this.product.refrencing_category_ids.split(',');
}
}
})
ul {
list-style: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="(id, index) in refCatIds" :key="index">
{{id}}
</li>
</ul>
</div>
Replace all of the commas with \n (newline character)
let string = "bc-men,bc-men-fashion,bc-men-underwear"
console.log(string.replace(/,/g, '\n'))
I want to bind and change multiple inputs to the same variable (so that they will always change together to the same value), but I can not figure this out. My code:
$(function () {
var AppVm = function () {
this.people = ko.observableArray([
{ firstName: 'Bert', lastName: 'Bertington' },
{ firstName: 'Charles', lastName: 'Charlesforth' },
{ firstName: 'Denise', lastName: 'Dentiste' }
]);
};
vm = new AppVm();
ko.applyBindings(vm);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<thead>
<tr><th>First name</th><th>Last name</th></tr>
</thead>
<tbody data-bind="foreach: people">
<tr>
<td>
<input type="text" data-bind="textInput: firstName"/>
</td>
<td>
<input type="text" data-bind="textInput: firstName"/>
</td>
</tr>
</tbody>
</table>
It loads the same value initially in both the text inputs, but when I change one of them , the other one does not update. How can I update them both at the same time?
As Jason Spake said in the comments:
Your properties must be observable in order to update with knockout.
Making the whole array observable doesn't make the individual
properties observable. ObservableArrays only react to changes in the
array size.
You therefore need to used ko.observable() inside the observableArray for the changes to be detected:
$(function () {
var AppVm = function () {
this.people = ko.observableArray([
{ firstName: ko.observable('Bert'), lastName: ko.observable('Bertington') },
{ firstName: ko.observable('Charles'), lastName: ko.observable('Charlesforth') },
{ firstName: ko.observable('Denise'), lastName: ko.observable('Dentiste') }
]);
};
vm = new AppVm();
ko.applyBindings(vm);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<thead>
<tr><th>First name</th><th>Last name</th></tr>
</thead>
<tbody data-bind="foreach: people">
<tr>
<td>
<input type="text" data-bind="textInput: firstName"/>
</td>
<td>
<input type="text" data-bind="textInput: firstName"/>
</td>
</tr>
</tbody>
</table>
Now when you change a value in the left input, in will change it in the right one as well at the same time.
I am using Laravel and trying to learn Vue.js. I have a delete request that is working properly and deleting the object from the database. The problem is that it is not being removed from the DOM after the successful deletion. I am using the $remove method and passing it the full object, so I know I'm missing something.
As a side note, I have a main.js as an entry point with a PersonTable.vue as a component. The PersonTable.vue holds the template and script for that template.
Here is my Laravel view:
<div id="app">
<person-table list="{{ $persons }}">
</person-table>
</div>
And here is my `PersonTable.vue:
<template id="persons-template">
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1>Persons List</h1>
<table class="table table-hover table-striped">
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Email</td>
<td>Gender</td>
</tr>
</thead>
<tbody>
<tr v-for="person in list">
<td>{{person.first_name }}</td>
<td>{{person.last_name }}</td>
<td>{{person.email }}</td>
<td>{{person.gender }}</td>
<td><span #click="deletePerson(person)">X</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
template: '#persons-template',
props: ['list'],
methods: {
deletePerson: function(person) {
this.$http.delete('/person/' + person.id).then(
function(response) {
this.persons.$remove(person);
}
);
}
},
created: function() {
this.persons = JSON.parse(this.list);
}
};
</script>
And my main.js entry point:
var Vue = require('vue');
Vue.use(require('vue-resource'));
var Token = document.querySelector('meta[name="_token"]').getAttribute('content');
Vue.http.headers.common['X-CSRF-TOKEN'] = Token;
import PersonTable from './components/PersonTable.vue';
new Vue({
el: '#app',
components: { PersonTable },
})
I think you need to bind this to the response function:
function(response) {
this.persons.$remove(person);
}.bind(this)
That way when you do this.persons you are still referring to the Vue component
edit: could try -
props:['personJson'],
data:function(){
return {
persons:[]
}
},
ready:function(){
this.persons = JSON.parse(this.personJson)
}
Thinking maybe since persons is a string initially, Vue isn't binding the reactive capabilities properly?
I think that you need to use the this.$set in your created method, if you don't, I am afraid that Vue would lose reactivity.
In your created method, could you try the following:
export default {
template: '#persons-template',
props: ['persons'],
methods: {
deletePerson: function(person) {
var self = this;
this.$http.delete('/person/' + person).then(
function(response) {
self.persons.$remove(person);
}
);
}
},
created: function() {
this.$set('persons',JSON.parse(this.persons));
}
};
Finally figured it out. I needed to pass the JSON data to my data property of the component. Here is the code.
In the blade file:
<div id="app">
<person-table list="{{ $persons }}">
</person-table>
</div>
In my PersonTable.vue file:
<template id="persons-template">
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1>Persons List</h1>
<table class="table table-hover table-striped">
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Email</td>
<td>Gender</td>
</tr>
</thead>
<tbody>
// Notice how I am using persons here instead of list
<tr v-for="person in persons">
<td>{{person.first_name }}</td>
<td>{{person.last_name }}</td>
<td>{{person.email }}</td>
<td>{{person.gender }}</td>
<td><span class="delete person" #click="deletePerson(person)"><i class="fa fa-close"></i></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
template: '#persons-template',
props: ['list'],
data: function() {
return {
persons: []
}
},
methods: {
deletePerson: function(person) {
this.$http.delete('/person/' + person.id).then(
function(response) {
this.persons.$remove(person);
}
);
},
},
created: function() {
// Pushing the data to the data property so it's reactive
this.persons = JSON.parse(this.list);
},
};
</script>
Thanks to everyone for their contributions. I almost ditched Vue because of how long it has taken to fix this error.