How mutate data in method computed? - vue.js

I'm on Vue 3. I have an onclick method which is supposed to modify the value of my props which is a boolean, I have tried several ways, I manage to enter the computed method, but the value of my props does not change
I register my data
data() {
return {
showConsommationWindow: false
}
}
then I tried 3 ways to change the value but none of them worked.
The first :
<submit-button v-on:click="showConsommationWindow = true" />
the 2nd : (alert is executed but the data value don't change)
<submit-button v-on:click="showConsommation(true)"/>
methods: {
showConsommation(boolValue){
alert('false')
this.showConsommationWindow = boolValue;
}
}
The last :
<submit-button v-on:click="showConsommation"/>
methods: {
showConsommation(){
if (!this.showConsommationWindow) {
alert('false')
this.showConsommationWindow = true;
return
}
this.showConsommationWindow = false;
}
},
I really don't understand why my data can't mutate, thanks for your help.

If value comes from a props, it means the parent distributes a boolean to the component. So if you want to change the boolean value, you should probably do:
// in parent
<Component :booleanValue="myBoolean" #changeBooleanValueEvent="changeMyBoolean" />
...
data() {
return {
myBoolean: true
}
}
methods: {
changeMyBoolean(value) {
this.myBoolean = value
}
}
// in component
props: {
booleanValue: {
...
}
}
methods: {
showConsommation() {
this.$emit('changeBooleanValueEvent', false)
}
}

Related

How to pass a computed as a prop to a component?

I have 1 component to which I pass a computed as a prop in this way:
<Datatable :extraParams="extraParams" />
the computed is in the attached image.
I'm having trouble with the value of this property: coverageSelected:coverageData
Coverage data is filled by a select multiple
The problem I have is that when selecting an element of the select, first the component function is executed, then the coverageSelected property is empty, then the computed is executed and until this moment the coverageSelected array is filled, then until the second attempt It already has a full array.
This is my computed
props: [
"status_selected",
"rows",
"totals",
"dateRangeValue",
"coverageSelected",
"coverageList",
"showAll",
"dateFilterSelected",
],
computed(){
extraParams() {
let coverageData = this.coverageList.filter((m) => this.coverageSelected.includes(m.variable));
return {
status: this.status_selected,
dateRange: this.dateRangeValue,
dateFilterSelected: this.dateFilterSelected,
coverageSelected: coverageData, //This is the property that is not late.
showAll: this.showAll,
};
},
}
Another detail to mention that this.coverageSelected is a prop
The method that is executed first in the computed is this:
async getList(params) {
this.loading = true;
try {
if (params) {
this.query = { ...this.query, ...params, ...this.extraParams, filters: this.filters };
} else {
this.query = { ...this.query, ...this.extraParams, filters: this.filters };
}
const { data } = await this.$axios.post(`${this.$config.routePrefix}${this.action}`, this.query);
if (data.code == 200) {
this.rows = data.rows;
this.total = data.total;
this.$emit("listed", data);
}
} finally {
this.loading = false;
}
},

Move Vue form input validation in component into a method

I have a Vue componenet for my input field. I have added some validation that makes sure only numbers are added. I added this on the oninput.
I'd like to move this to a method so I can add more checks (eg. if Type !== number)
This works well, but with the validation inline:
<input
v-bind="$attrs"
v-on="{
...$listeners,
input: event => $emit('input', event.target.value)
}"
oninput="this.value = Math.abs(this.value)"
/>
This is how I would like it (but current the validation is not working):
<input
v-bind="$attrs"
v-on="{
...$listeners,
input: event => handleInput(event.target.value)
}"
/>
methods: {
handleInput(value) {
console.log(value);
// 1st emit
this.$emit("input", value);
// 2nd Validate -- Not working...
this.value = Math.abs(this.value);
}
}
Any ideas on how I get this.value = Math.abs(this.value); to feed back into the input?
UPDATE
Thanks to a helpful comment I made some progress. The below code works for the first character but not for ongoing characters.
If numbers are typed, then validation passes true and input emitted.
If 1 character (eg. a) is typed then we emit the number 0. If a second character is inputted then the char is emitted (eg. press b and now the input is 0b)
I can see the this.$emit("input", 0) is triggered, so not sure why char emitted.
methods: {
validateInput(value) {
// if it type isnt set as a number then leave
if (this.type != "number") {
return true;
}
// check if value a number
if (Math.abs(value)) {
return true;
}
return false;
},
handleInput(value) {
if (this.validateInput(value)) {
this.$emit("input", value);
} else {
this.$emit("input", 0);
}
}
}
If you want to check a value before emitting the input event, you could do it like this:
methods: {
validateInput(value) {
if (typeof value !== 'number') { return false; } // check if it's not a string
if (value !== Math.abs(value)) { return false; } // check if value is positive
return true
}
handleInput(value) {
if (this.validateInput(value)) { this.$emit("input", value); }
this.$emit("input") // if value is not a valid input, you may want to do nothing, or emit merely that the event happened.
}
}
A better way of doing a custom input would be to use the value prop of an input, and bind it to a dynamic property in your component, for example by using v-model="value". Fun fact: v-model has a modifier v-model.number which would do exactly what you need.
The only caveat is that you can't directly modify props, so you'd need to use a computed property as a way to automatically handle the 'getting and setting' of your form's value.
// CustomInput.vue
<template>
<input v-bind="$attrs" v-on="$listeners" v-model.number="localValue" />
</template>
<script>
export default {
props: {
value: {
type: Number,
required: true,
}
}
computed: {
localValue: {
get() { return this.value; }
set(newVal) { this.$emit('input', newVal); }
}
}
}
</script>
You don't need to make a custom component for this case. You could simply use v-model.number in the parent and it would work. Once your inputs get more complex, you want to modify the set method a bit to set(newVal) { if (this.validateInput(newVal)) {this.$emit('input', newVal);} }, defining your own 'validateInput' method.
If you find you're writing a lot of different validations for different use cases, look into libraries like Vuelidate and VeeValidate

Prevent Vue Multiple Select to Store an Empty Array

I want this select multiple to pre-select one option, and not be able to deselect all options.
Whenever the last selected option is deselected it should be reselected. In other words when the user tries to deselect the last selected option it should visually not be deselected.
<template>
<b-select
if="Object.keys(doc).length !== 0 /* wait until firebase has loaded */"
:options="computedOptions"
v-model="model"
multiple
#input="onChange"
/>
</template>
<script>
//import Vue from 'vue'
import { fb } from "../fbconf";
export default {
name: "MyMultiSelect",
props: {
doc: Object, // firestore document
},
data() {
return {
options: []
};
},
firestore() {
var options = fb.db.collection("options");
return {
options: options
};
},
computed: {
computedOptions: function() {
return this.options.map(function(option) {
return {
text: option.name,
value: option.id
};
});
},
// to make sure mySelectedOptions is an array, before this.doc is loaded
// I use the following custom model
// because not using 'get' below causes a warning:
// [Vue warn]: <select multiple v-model="localValue"> expects an Array value for its binding, but got Undefined
model: {
get: function() {
if (!this.doc.hasOwnProperty('mySelectedOptions')) return []; // empty array before this.doc is loaded
else return this.doc['mySelectedOptions'];
},
set: function(newValue) {
// here I can prevent the empty array from being stored
// but visually the user can deselect all options, which is bad UX
//if (Array.isArray(newValue) && newValue.length > 0) this.doc['mySelectedOptions'] = newValue;
}
},
},
methods: {
onChange: function(newValue){
// I can manually store the array as I want here
// but I cannot in any way prevent the user from deselecting all options
if (Array.isArray(newValue) && newValue.length > 0) this.doc['mySelectedOptions'] = newValue;
else {
// none of these reselects the last selected option
var oldValue = this.doc['mySelectedOptions'];
this.doc['mySelectedOptions'] = this.doc['mySelectedOptions'];
//this.$forceUpdate();
//this.$emit("change", newValue);
//Vue.set(this.doc, 'mySelectedOptions', this.doc['mySelectedOptions']);
}
}
}
};
</script>
You could add watcher and when length becomes 0 just add previous value.
watch: {
model(val, oldVal) {
if(val.length == 0 && oldVal.length > 0) {
// take only one item in case there's clear button or etc.
this.model = [oldval[0]];
}
}
}

Vue / Vuex : paste event triggered before input binded value is updated

I have a simple form in a component :
<form v-on:submit.prevent="submitSearch">
<input v-model="objId" #paste="submitSearch">
<button>Submit</button>
</form>
and
var searchForm = {
methods : {
submitSearch() {
store.dispatch('submitSearch')
}
},
computed : {
objId: {
get () {
return ...
},
set (id) {
store.commit('objId', id)
}
}
},
...
};
It works well when typing and submitting, however when pasting a value submitSearch is called just before objId is updated so it doesn't. Is there a consise and vue-friendly way to handle this?
One way you could do it is have a local variable isPaste and set it to true, when the paste event is triggered. Then also register an input event which will trigger after the paste event and check if isPaste is true. If it is, then submit and set isPaste to false again.
<input v-model="objId" #paste="paste" #input="input">
data(): {
return {
isPaste: false
}
},
methods: {
paste() {
this.isPaste = true;
},
input() {
if (this.isPaste) {
store.dispatch('submitSearch');
isPaste = false;
}
}
}
Solved it using nextTick() :
submitSearch() {
Vue.nextTick().then(function () {
store.dispatch('submitSearch')
})
}
Not sure if it's the recommended way but it works well and avoid extra variables.

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.