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

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.

Related

How mutate data in method computed?

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)
}
}

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

Want to update my input value with v-model

I am trying to change the value of an hidden input with vue js. I added a v-model to the input and I am trying to update it in a method.
My input looks like this:
<input type="hidden" name="payment_method" v-model="payment_method">
My vue js:
data: function() {
return {
name: '',
payment_method: '',
.
.
.
methods: {
submitAddPaymentMethod(){
window.stripe.handleCardSetup(
this.clientSecret, card, {
payment_method_data: {
//billing_details: { name: cardHolderName.value }
}
}
).then(function(result) {
if(result.error) {
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// has no effect on my input
this.payment_method = result.setupIntent.payment_method;
this.$refs.form.submit();
}
}.bind(this));
Maybe someone has an idea how to do this. Would appreciate that!
Best
You are getting a different this in the promise callback than the one you're expecting.
Use fat arrow to solve this.
.then(result => {
if(result.error) {
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// has no effect on my input
this.payment_method = result.setupIntent.payment_method;
this.$refs.form.submit();
}
}
I needed to add nextick() to wait for the DOM to be updated.

VueJS: Adding a class to one element if a class exists on another element

I'm working in VueJS. I'm trying to bind a class to one element based on an existence of a class on another element. The below is in a :for loop to print out the list.
The '#accordion-'+(index+1)) is the id of the div I want to check to see if a class exists on it.
I wrote a method and it works UNTIL I check the element's classList. Right now, I'm only doing a console log, but eventually this will return true and hopefully the class will apply.
methods: {
existingTopic: function(lcDivID) {
const element = document.querySelector(lcDivID);
console.log(element); //It gives me the element.
/* The below is where it crashes */
console.log(element.classList.contains("collapsePanelExistingTopic"));
}
}
I find it so frustrating. I've spent a day on this without any results. Any help you can provide it would be great.
Here it is, you can also use this.$el as document
...
methods: {
hasClass() {
const element = this.$el.querySelector('h1')
if (element.classList.contains('your-class-here')) {
console.log(true)
this.$el.querySelector('anotherelement').classList.add("your-another-class");
} else {
console.log(false)
}
}
},
mounted() {
this.hasClass()
}
...
Alternative
<h1 class="mb-5 fs-lg" ref="myElement">Sign up</h1>
...
methods: {
hasClass() {
const element = this.$refs.myElement
if (element.classList.contains('mb-5')) {
console.log(true)
this.$el.querySelector('anotherelement').classList.add("your-another-class");
} else {
console.log(false)
}
}
},
mounted() {
this.hasClass()
}
...
So you can define ref as :ref="index+1" in your loop

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.