Value of input not changed - vue.js

Using Vue3 and Vuex4
I got an input field:
<input :class="invalid.includes(item.attribute) ? 'invalidInput' : 'validInput'" type="text" :id="item.attribute" :name="item.attribute" :placeholder="item.default_value" min="0" step="any" :value="item.value" #input="validate(item.attribute, $event)" class="p-1">
I change the value of "invalid" like this. Just checking for the validity of a regex and adding/removing the attribute to the array.
VALIDATE_INPUT: (state, data) => {
var regex = /(?=.*\d)^\$?(([1-9]\d{0,2}(,\d{3})*)|0)?(\.\d{1,2})?$/;
switch (data.attribute) {
case 'invoice_amount_f':
if (!regex.test(data.value)) {
state.validations.push(data.attribute)
} else {
let index = state.validations.findIndex(el => el === data.attribute);
if (index > -1) {
state.validations.splice(index, 1);
}
}
break;
default:
break;
}
}
The action calling the mutation is called like:
const validate = (attribute, event) => {
store.dispatch('validate', {
attribute: attribute,
value: event.target.value
});
}
Computed:
var invalid = computed({
get() {
return store.getters.getValidationState;
}
});
When now typing something into the input field the text in the field ain't chaning. This seems to happen cause I use the value of invalid inside the template. Why is that?
EDIT: It seems to have something to do with the scope I am using it in.
<h3>{{ invalid }}</h3>
<div v-if="nestedListItems && Object.keys(nestedListItems).length !== 0">
<draggable v-model='nestedListItems' item-key="id" class=" w-12/12 bg-white m-auto border" animation="150">
When rendering it outside of draggable it's absolutely fine. Inside it crashes my store.

You need to provide an object to the :class, see here:
https://v3.vuejs.org/guide/class-and-style.html#binding-html-classes
I suggest you create a variable in the script containing the boolean e.g. isValid and then apply it like this:
:class="{invalidInput : !isValid, validInput: isValid }"

Related

Using computed with dynamic objects in Vue JS

Hey gang
Since I'm kind of new to Vue JS, I've managed to almost complete a simple staged form project, I'm having trouble with a dynamic object
Assuming that this is the last stage of the form:
<template v-if="currentStage === 3">
<h2>Step 3 - Additional Params</h2>
<h3>Please fill all the parameters needed</h3>
<div v-for="(param, key, index) in params" :key="key">
<label class="inputLabel" :for="key">Please fill in {{ key }} param</label> <span class="redStar">*</span>
<input v-model="params[key]" :id="key">
<span v-if="submitted && $v.params[key] && !$v.params[key].required" class="invalid-feedback">It's a required field, please fill it</span>
<!-- <span v-if="v$.params[key].$errors[0]" class="invalid-feedback"> {{ v$.params[key].$errors[0].$message }} </span>-->
</div>
<button #click="updateStage(0)">Previous</button>
<button #click="handleLastSubmit">Create</button>
</template>
Inside Data() I created an empty object destined to be fulfilled based on user input from certain field in the form, as suggested in the comment below:
params() {
if (this.jsonS.hasOwnProperty(this.tDetails.platform)) {
for (const [key, value] of Object.entries(this.jsonS[this.tDetails.platform])) {
this.params[key] = value;
}
}
return this.params;
},
I tried to add { required } based on vue forums suggestions like this(inside computed):
addRequiredValidation() {
//Here I need somehow to add validations to params Object.
for (const key in this.integrationParams) {
this.$options.validations().integrationParams[key].required = required
}
}
And implement it in the validations as follows:
validations() {
return {
integrationParams: this.addRequiredValidation,
trafficDetails: {
brand: {required, minLength: minLength(3)},
platform: {required, minLength: minLength(3)},
whitelabel: {required, minLength: minLength(3)},
country: {required, minLength: minLength(2)},
campaignName: {required, minLength: minLength(2)}
}
}
},
Ending up getting this error:
TypeError: Cannot convert undefined or null to object
at Function.keys ()
You could use another computed field to get your params.
computed: {
params() {
const params = {};
if (this.jsonS.hasOwnProperty(this.tDetails.platform)) {
for (const [key, value] of Object.entries(this.jsonS[this.tDetails.platform])) {
params[key] = value;
}
}
return params;
}
}
The params should be recalculated whenever this.tDetails.platform changes (i.e., a user inputs another platform) and your addRequired property should update correctly.

Returned Value From Computed Property Doesn't Change When It's Used In Template (Vue3 without Composition API)

I'm trying to get an item's width in a computed property to pass it to CSS variable.
<div class="wrapper">
<fieldset>
<legend :style="`--gap:${legendGap}px`"></legend>
</fieldset>
<label>{{ label }}</label>
<input/>
</div>
computed: {
legendGap() {
if (
!this.$el ||
this.label == '' ||
!this.design == 'outlined' ||
!this.options.some((x) => ['has-placeholder', 'is-dirty', 'is-focused'].includes(x))
) {
return 0;
}
return (
parseFloat(
window.getComputedStyle(this.$el.querySelector('label')).getPropertyValue('width')
) * 0.8
);
}
If I remove :style="`--gap:${legendGap}px`" from template or change the variable name, computed property returns correct value, otherwise it returns 0. So somehow, using the computed property in the template locks its value. I haven't seen anything like that before. I assume that using $el causes the problem, but I'm not sure.
How can I make the computed property returns the correct value without removing the variable from template?
UPDATED, sorry I fixed a few script errors, I think it should be good now, let me know if you still have issues.
=========
Maybe try a different approach like this, I just hand coded it, you may double check for typos etc.
<div class="wrapper">
<fieldset>
<legend :style="`--gap:${legendGap}px`"></legend>
</fieldset>
<label ref="label">{{ label }}</label>
<input/>
</div>
data() {
return {
legendGap: 0
}
},
watch: {
label() {
this.calcLegendGap()
}
},
mounted() {
// needed to set the init value after all rendered
this.calcLegendGap()
},
methods: {
calcLegendGap() {
if (
!this.label ||
this.design !== 'outlined' ||
!this.options.some((x) => ['has-placeholder', 'is-dirty', 'is-focused'].includes(x))
) {
this.legendGap = 0;
} else {
// $nextTick is required we need to wait for DOM update
this.$nextTick(() => {
this.legendGap = parseFloat(
window.getComputedStyle(this.$refs.label).getPropertyValue('width')
) * 0.8;
});
}
}
}

Vue Vuelidate to validate unique value based on data from server

I am trying to create a form with vuelidate. In one field I would like to check if the name is taken or not. I have some async methods to get names and ids from the server and assigning them to arrays, and I have a custom validator that checks if the name exists, either by checking the includes in the array, or by checking a computed value that already checks the array.
Neither of the methods seems to work however. If I check the array, its seems to be empty since it always returns false (even tho the array has values according to the vue tools in the browser). If I check the the computed value, I get an error with undefined.
So my question is, what is the simplest why to validate whether a value exists, and why isn't my current code wokring?
<template>
<div>
<form class="needs-validation" #submit.prevent="submitForm" method="post">
<div class="form-group row">
<label class="col-sm-2 col-form-label">Name:</label>
<div class="col-sm-10">
<input
type="text"
class="form-control"
:class="{ 'is-invalid': $v.form.name.$error }"
id="name"
placeholder="enter name"
v-model="form.name">
<span
class="text-danger"
v-if="!$v.form.name.required && $v.form.name.$dirty">name is required</span>
<span
class="text-danger"
v-if="!$v.form.name.isUnique && $v.form.name.$dirty">name not unique</span>
</div>
</div>
</form>
</div>
</template>
<script>
import { required } from 'vuelidate/lib/validators'
const isUnique = (value, vm) => {
if (value === '') return true
if (vm.names) return !vm.names.includes(value)
return true
}
export default {
data() {
return {
form: {
name: ""
}
ids: [],
names: []
}
}
validations: {
form: {
name: {
required,
isUnique
}
}
}
async created() {
try {
const response = await this.$http.get('/get_data/?fields=id,name')
var array_id = []
var array_name = []
for (var data of response.data) {
array_id.push(data['id'])
array_name.push(data['name'])
}
this.ids = array_id
this.names = array_name
}
}
}
<script>
Seem like you miss the right way to write down methods
form: {
name: {
required,
isUnique : this.isUnique
}
}
},
methods: {
isUnique = (value, vm) => {
if (value === '') return true
if (vm.names) return !vm.names.includes(value)
return true
}
}

Why are checkboxes not reset by v-model?

This is what i have:
Template
<div
v-for="(filter, index) in filtersList"
:key="index"
class="option-block"
>
<label
v-for="value in filter.values"
:key="value.id"
class="option-block__container"
>
{{ value.title }}
<input
type="checkbox"
v-model="filtersValues[filter.name]"
:value="value.value"
>
<span class="option-block__checkmark"></span>
</label>
</div>
And the part of my vue code:
data() {
return {
filtersList: {},
filtersValues: {}
}
},
beforeMount() {
this.loadInitData();
this.initFilters();
},
methods: {
loadInitData() {
const data = JSON.parse(this.$el.getAttribute('data-data'));
this.filtersList = data.filters;
},
initFilters() {
for (let i in this.filtersList) {
if (!this.filtersList.hasOwnProperty(i)) {
continue;
}
this.filtersValues[this.filtersList[i].name] = [];
}
}
}
It works, but when i call initFilters() method again (for reseting) checkboxes are still selected, and i don't know why.
The way you are assigning new, empty arrays to filterValues is not reactive.
If you change your initFilters to assign an entire new value to filterValues, you don't need to worry about using Vue.set(). For example
initFilters() {
this.filtersValues = this.filtersList.reduce((vals, { name }) => ({
...vals,
[ name ]: []
}), {})
}
Demo ~ https://jsfiddle.net/cjx09zwt/
Where did filter.values come from in line 2 of template?
Anyways vue would not be able to track the changes you are making (judging from the visible code)
There are some caveats to vue 2's reactivity. Check here for more info.
TLDR; you will need to declare anything you want to be made reactive in the component's data option upfront.
HTH

v-text-field returns string after typing even though it has type number

<VTextField
:value="addOnStartingPrice"
solo
outline
reverse
append-icon="attach_money"
type="number"
min="0"
step="any"
#input="$emit('update:addOnStartingPrice', $event)"
/>
I have something like this.
Question 1) as soon as i change the number or type in it, it throws number value correctly, but its type is String instead of number, so type check gets failed. How do I get it to return value as type:number?
Question 2) what exactly does step="any" and min="0" do? They don't work I guess, because i can type negative numbers too.
Indeed vuetify returns string. As a workaround you can use number modifier.
<v-text-field type="number" v-model.number="computedAddOnStartingPrice" />
And you can have a computed property like this:
computed: {
computedAddOnStartingPrice: {
get () { return this.addOnStartingPrice },
set (newVal) { this.$emit('update:addOnStartingPrice', newVal) }
}
}
As per the step attribute read Html input step.
And for min attr read Html input min
You can also use vuetify rules to check if the user has entered a positive number. Example:
<v-text-field
type="number"
step="any"
min="0"
:rules="[numberRule]"
v-model.number="computedAddOnStartingPrice"
></v-text-field>
The numberRule:
data: () => ({
//...
numberRule: val => {
if(val < 0) return 'Please enter a positive number'
return true
}
})
See it in action
The issue lies here as the value property of InputElements is string.
And it gets casted when needed
One thing you can do is "fix" by extend/override:
import * as comps from "vuetify/es5/components";
...
const override = comps.VTextField.extend({
onInput(e) {
const target = e.target;
if (this.type !== "number") this.internalValue = target.value;
else {
this.internalValue = target.valueAsNumber;
}
this.badInput = target.validity && target.validity.badInput;
}
});
Vue.component('NewVTextField, override);
Or "workaround" by $emit(+$event), as this seems to be the intended path
But i guess you should use the setter, getter aproach - as you can also chain validators in the setter
check: #roli loli:
computed: {
computedAddOnStartingPrice: {
get () { return this.addOnStartingPrice },
set (newVal) { this.$emit('update:addOnStartingPrice', newVal) }
}
}
Just convert in to Number in #input handler.
<VTextField
:value="addOnStartingPrice"
solo
outline
reverse
append-icon="attach_money"
type="number"
min="0"
step="any"
#input="$emit('update:addOnStartingPrice', Number($event))"
/>
<input v-model.number="price" type="number">
data() {
return {
price: '',
}
},
will save integer
I was able to use v-on:change Event to convert my input. I wasn't able to get
Andrew Vasilchuk's answer to work.
<v-text-field
v-model="item.quantity"
label="Quantity"
type="number"
v-on:change="item.quantity = parseFloat(item.quantity)"
></v-text-field>