Vue.js: Binding two input to each other - vue.js

I'm currently starting on using vue.js, and have encountered a situation.
I wish to bind two inputs for example C = A - B. and B = A - C, where A is constant and a change in B or C will affect the other.
I successfully bind C using v-model and placing it in computed. However, when I tried the same for B, it ran into an infinite loop.
This should be something very simple however, I can't seem to find a solution to it.
Any help is appreciated thank you!
Edit: Code is included below. I wish for use the be able to key into either down_payment or loan_amount. After which it will calculate the other value automatically. However this way seems to make it go into a infinite loop
<input type="number" v-model="down_payment" class="form-control-right" placeholder="Downpayment" value="{{ down_payment }}" number>
<input type="number" v-model="loan_amount" placeholder="Loan Amount" value="{{loan_amount }}" number>
My javascript
new Vue({
el: '#calculator',
data: {
asking_price: 60000,
},
computed: {
loan_amount: function(){
return this.asking_price - this.downpayment;
},
down_payment : function(){
return this.asking_price - this.loan_amount;
},
}
});

You really have two independent variables and one that is computed but needs to be writable and handle the dependencies in its setter.
data: {
asking_price: 60000,
down_payment: 20
},
computed: {
loan_amount: {
get: function() {
return this.asking_price - this.down_payment;
},
set: function(newValue) {
this.down_payment = this.asking_price - newValue;
}
}
}
Fiddle

I'm still learning vue myself, but I'm wondering if it would be better to just define b and c as data properties and then use a watch to recompute one or the other when there's a change (instead of using computeds). Something like this:
var vm = new Vue({
el: '#calculator',
data: {
a: 10,
b: 0,
c: 10
},
watch: {
'b' : function(newVal, oldVal) {
this.c = this.a - newVal;
},
'c' : function(newVal, oldVal) {
this.b = this.a - newVal;
}
}
});

Related

Multiple dependant variables in Vue

What is the simplest way to implement multiple changeable variables that depend on each other?
For example we have a price before discount which cannot change and we can:
apply the discount and the price after discount should update,
or change the price after discount and the discount should update accordingly.
I have came up with the following solution for this example: https://jsfiddle.net/2wh6cgq5/1/
Can it be done without having to create separate handlers for each #input event and applying the v-model directive?
One option is to use the v-model binding with computed properties so you can control the set logic.
new Vue({
el: '#app',
data: {
priceBeforeDiscount: 100,
discount: 50,
priceAfterDiscount: 50,
},
computed: {
priceAfterDiscountInput: {
get() {
return this.priceAfterDiscount;
},
set(val) {
this.priceAfterDiscount = val;
this.discount = this.priceBeforeDiscount - this.priceAfterDiscount;
}
},
discountInput: {
get() {
return this.discount;
},
set(val) {
this.discount = val;
this.priceAfterDiscount = this.priceBeforeDiscount - this.discount;
}
}
},
})
Another possibility is to use watchers on discount and priceAfterDiscount. It doesn't lead to an infinite loop in this case because the values reach an equilibrium and watchers only run if the value changes. I'd be cautious about using co-dependent watchers in general though.
new Vue({
el: '#app',
data: {
priceBeforeDiscount: 100,
discount: 50,
priceAfterDiscount: 50,
},
watch: {
discount() {
this.priceAfterDiscount = this.priceBeforeDiscount - this.discount;
},
priceAfterDiscount() {
this.discount = this.priceBeforeDiscount - this.priceAfterDiscount;
}
},
})
However, I don't really think there's an issue with your solution. If you aren't required to use the v-model directive (e.g. for vee-validate), I'd just convert it to v-bind and do the assignment in the input handler.

Why it is hard to use vue-i18n in vue data() (why it is not reactive)

I am using vue-i18n in a vue project. And I found it really confusing when using some data in vue data with i18n. Then if I change locale, that data is not reactive. I tried to return that data from another computed data but anyways it is not reactive because i18n is written in data. *My situation - * I want to show table with dropdown(list of columns with checkbox) above it. When user checks a column it will be showed in table if unchecks it won't. It is working fine until I change locale. After changing locale table columns is not translated but dropdown items is reactively translated and my code won't work anymore. Here is some code to explain better: In my myTable.vue component I use bootstrap-vue table -
template in myTable.vue
<vs-dropdown vs-custom-content vs-trigger-click>
<b-link href.prevent class="card-header-action btn-setting" style="font-size: 1.4em">
<i class="fa fa-th"></i>
</b-link>
<vs-dropdown-menu class="columns-dropdown">
<visible-columns :default-fields="columns" #result="columnListener"></visible-columns>
</vs-dropdown-menu>
</vs-dropdown>
<b-table class="generalTableClass table-responsive" :fields="computedFieldsForTable">custom content goes here</b-table>
script in myTable.vue
data(){
return {
fieldsForTable: [];
}
},
computed: {
computedFieldsForTable () {
return this.fieldsForTable;
},
columns() {
return [
{
key: 'id',
label: this.$t('id'),,
visible: true,
changeable: true
},
{
key: 'fullName',
label: this.$t('full-name'),,
visible: true,
changeable: true
},
{
key: 'email',
label: this.$t('email'),,
visible: true,
changeable: true
}
]
}
},
mounted () {
this.fieldsForTable = this.filterColumns(this.columns);
},
methods: {
filterColumns(columns = []) {
return columns.filter(column => {
if (column.visible) {
return column
}
})
},
columnListener ($event) {
this.fieldsForTable = this.filterColumns($event)
}
}
Can someone give me some advice for this situation ?
*EDIT AFTER SOME DEBUGGING: I think when filtering columns(in computed) and returning it for fieldsForTable inside filterColumns(columns) method, it actually returning array(of objects) with label='Label Name' not label=this.$t('labelName'). So after filtering the new array has nothing to do with vue-i18n. My last chance is reloading the page when locale changes.
Trying modify computedFieldsForTable as follows. You need to reference this.columns in computedFieldsForTable, so that Vue can detect the change of labels in this.columns.
computedFieldsForTable () {
return this.filterColumns(this.columns);
},
EDITED: put your this.columns in data. Then
columnListener ($event) {
this.columns = $event;
}
I hope i didn't misunderstand what you mean.
EDITED (again):
Maybe this is the last chance that I think it can work. Put columns in computed() still and remove computedFieldsForTable. Finally, just leave fieldsForTable and bind it on fields of <b-table>.
watch: {
columns(val) {
this.fieldsForTable = this.filterColumns(val)
}
},
method: {
columnListener ($event) {
this.fieldsForTable = this.filterColumns($event)
}
}
However, I think it is better and easier to reload page whenever local change. Especially when your columns have a more complex data structure.

How does Vue choose whether a select option will become selected when app starts?

I have this app: https://jsfiddle.net/punter/ggatev5k/1/ , also posted here:
<div id="app">
<select v-model="teamsSelected">
<option v-for="team in teams" :value="{id: team.id, name: team.name}">{{team.name}}</option>
</select>
</div>
<script>
new Vue({
el: '#app',
data: {
teams: [
{id: 1, name: 'Bayern'},
{id: 2, name: 'Manchester'},
{id: 3, name: 'Barcelona'},
{id: 4, name: 'PAOK'},
],
teamsSelected: {id: 2, name: 'Manchester'},
},
});
</script>
I was surprised to see that an option became selected, even though the === does not return true between this.teams[1] and this.teamsSelected.
Therefore Vue doesn't rely on === to see if it will select by default an option from <select>. It has some other way to decide. Which way is that? Is that way described anywhere on the web, in the official documentation or elsewhere?
Thank you.
Because in the code where Vue decides if an option is selected:
function setSelected (el, binding, vm) {
actuallySetSelected(el, binding, vm);
It actually uses a looseEqual() function to compare:
function actuallySetSelected (el, binding, vm) {
// ...
for (var i = 0, l = el.options.length; i < l; i++) {
// ...
if (isMultiple) {
// ...
} else {
if (looseEqual(getValue(option), value)) {
if (el.selectedIndex !== i) {
el.selectedIndex = i;
}
return
}
// ...
}
Which, you can infer by the name, does a loose equality check:
/**
* Check if two values are loosely equal - that is,
* if they are plain objects, do they have the same shape?
*/
function looseEqual (a, b) {
The reason?
Using a looser equality comparison is just a design decision, naturally. But I could track it and the looseEqual usage was added to fix an issue (#3673).
This is because you used v-model directive
https://v2.vuejs.org/v2/api/#model
The selected option will be the one that matches the model. If no option value matches the model, you won't have anything selected by default.

Vue.js 2 - $forceUpdate() on components doesn't refresh computed properties?

I'm not sure if I'm doing this right or wrong, but all the answers I seem to find how to update the dom for computed values...
I have this component:
Vue.component('bpmn-groups', {
props: ['groups', 'searchQuery'],
template: '#bpmn-groups',
computed: {
filteredGroups: function () {
var self = this;
return this.groups.filter(function(group) {
self.searchQuery = self.searchQuery || '';
return _.includes( group.name.toLowerCase(), self.searchQuery.toLowerCase() );
});
}
},
methods: {
clearFilter: function () {
this.searchQuery = '';
},
deleteGroup: function(group) {
Vue.http.delete('api/groups/'+group.id ).then(response => { // success callback
var index = this.groups.indexOf(group); // remove the deleted group
this.groups.splice(index, 1);
this.$forceUpdate(); // force update of the filtered list?
toastr.success('Schemų grupė <em>'+group.name+'</em> sėkmingai pašalinta.');
}, response => { // error callback
processErrors(response);
});
this.$forceUpdate();
},
},
});
And in the template I just have a simple v-for to go through filteredGroups:
<input v-model="searchQuery" type="text" placeholder="Search..." value="">
<div v-for="group in filteredGroups" class="item">...</div>
The deletion works fine, it removes it from groups property, however the filteredGroups value still has the full group, until I actually perform a search or somehow trigger something else...
How can I fix it so that the filteredGroup is updated once the group is updated?
Don't mutate a prop - they are not like data defined attributes. See this for more information:
https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
Instead, as recommended in the link, declare a local data attribute that is initialized from the prop and mutate that.

Two-way filter updating on the fly | Vue.js

How one can do custom two-way filter for model, updating on the fly in Vue.js.
The following code example from docs works on input blur. But I need it work on keypress.
Vue.filter('currencyDisplay', {
read: function(val) {
return '$'+val.toFixed(2)
},
write: function(val, oldVal) {
var number = +val.replace(/[^\d.]/g, '')
return isNaN(number) ? 0 : parseFloat(number.toFixed(2))
}
})
Many thanks in advance for any help!
You can apply a filter to a Vue data property by creating a computed property with a get and set method that fire the read and write methods of the filter, respectively:
data() {
return {
foo: 0,
}
},
computed: {
filteredFoo: {
get() {
return Vue.filter('currencyDisplay').read(this.foo);
},
set(value) {
this.foo = Vue.filter('currencyDisplay').write(value);
}
}
}
Here's a working fiddle.