Validating dynamic array in vuelidate - vue.js

I am very new to vue and I am trying to validate an array using vuelidate which is used to render a dynamic table. The problem is with the validation() method as I can comprehend.
According to vuelidate docs, https://vuelidate.js.org/#sub-collections-validation, the $each method supports array. When I use it, the validation never fails. However, when I omit $each, and, try to validate first index of the array, it returns as corrected - validation fails.
To be more precise, I am trying to validate each added row(s), and if there's a problem with the validation, it'd add a class to the affected row.
Component App.vue,
This is the HTML code:
<template>
<div>
<br>
<div class="text-center">
<button type="button" class="btn btn-outline-primary" #click="addRow()">Shto</button>
</div>
<br>
<p
v-for="error of v$.$errors"
:key="error.$uid"
>
<strong>{{ error.$validator }}</strong>
<small> on property</small>
<strong>{{ error.$property }}</strong>
<small> says:</small>
<strong>{{ error.$message }}</strong>
</p>
<form name="articles" #submit.prevent="submitForm">
<table class="table table-hover table-responsive">
<thead class="thead-inverse">
<tr>
<th>Nr.</th>
<th class="col-3">Numri</th>
<th class="col-4">Përshkrimi</th>
<th class="col-1">Sasia</th>
<th class="col-1">Çmimi</th>
<th class="col-2">Shuma</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(item, idx) in items_table" :key="item">
<td>
{{idx+1}}
</td>
<td>
<input v-model="item.part_no" name="part_no[]" class="form-control" type="text" />
</td>
<td>
<textarea v-model="item.part_name" name="part_name[]" class="form-control" type="text"></textarea>
</td>
<td>
<input v-model="item.part_qty" name="part_qty[]" class="form-control " type="number" step="0.01">
</td>
<td>
<input v-model="item.part_price" name="part_price[]" class="form-control" type="number" step="0.01">
</td>
<td>
<input :value="item.part_total" name="part_total[]" class="form-control text-center border-0" style="background-color: transparent; font-size: 18 px;" type="text" disabled>
</td>
<td>
<button type="button" #click="deleteRow(idx)" class="btn btn-danger btn-md btn-block">X</button>
</td>
</tr>
</tbody>
</table>
<div class="text-center">
<button type="submit" name="" id="" class="btn btn-primary btn-lg btn-block">Valido</button>
</div>
</form>
<div class="text-center">
<textarea name="" id="verbose_log" cols="70" rows="15" refs="logg"></textarea>
</div>
</div>
</template>
This is the content from script tag:
<script>
import useVuelidate from '#vuelidate/core'
import { required } from '#vuelidate/validators'
export default {
name: 'App',
setup: () => ({ v$: useVuelidate() }),
validation() {
return {
items_table: {
$each: {
part_no: {
required,
}
}
},
}
},
data() {
return {
items_table: [
{
part_no: '', part_name: '', part_qty: '', part_price: '', part_total: ''
}
],
items_subtotal: 0.00,
items_total: 0.00,
}
},
methods: {
deleteRow(index) {
if(this.items_table.length == 1) {
this.items_table.splice(index, 1);
this.items_subtotal = 0.00;
this.items_total = 0.00;
this.addRow();
} else if(this.items_table.length > 0) {
this.items_table.splice(index, 1);
}
},
addRow() {
this.items_table.push({part_no: '', part_name: '', part_qty: '', part_price: '', part_total: ''});
},
submitForm() {
this.v$.$touch();
//if (this.v$.$error) return;
console.log(this.v$);
document.getElementById('verbose_log').innerHTML = JSON.stringify(this.$data, null, 4);
}
},
computed: {
//
}
}
</script>
For the sake of clarity, I have excluded two methods which calculate line total and the total itself.

Related

Getting a NaN when typing a value

I'm trying to calculate a value from 2 input boxes and then get the total of those input boxes. I'm then trying to get the all my amounts and total them and add them to the subtotal but the issue I'm having is that when I type in a number in the first box my output is NaN instead of 0 and I would like for it to show me a 0 instead.
Here is my code
<template>
<div class="content-header">
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-12">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Unit</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products">
<td>{{ product['name'] }}</td>
<td>
<input type="text" class="form-control" v-model="unit[product['unit']]" #change="calculateCost(product['name'])">
</td>
<td>
<input type="text" class="form-control" v-model="price[product['price']]" #change="calculateCost(product['name'])">
</td>
<td>
{{ cost[product['name']] }}
</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>
Subtotal: {{ subTotal }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default{
props: [],
data(){
return {
products: [],
unit: {},
price: {},
cost: []
}
},
computed:{
subTotal(){
if(this.cost!== null)
{
if(Object.keys(this.cost).length !== 0){
return Object.keys(this.cost).reduce((carry, item) => {
carry+= Number(this.cost[item])
return carry;
}, Number(0))
}else{
return 0;
}
}
}
},
methods: {
getProducts(){
axios.get(`/api/product/all`).then(response => {
this.products = response.data.products;
});
},
calculateCost(item){
this.cost[item] = Number(this.unit[item]) * Number(this.price[item]);
},
},
mounted() {
this.getProducts();
}
}
</script>
Almost all type of inputs return a string. You can use
<input type="number" class="form-control" v-model="unit[product['unit']]" #change="calculateCost(product['name'])">
or
<input type="text" class="form-control" v-model.number="unit[product['unit']]" #change="calculateCost(product['name'])">
The problem is the v-model for unit and price are set to the different keys than the one given to calculateCost(), which causes the lookups to fail and results in NaN:
<input v-model="unit[product['unit']]" #change="calculateCost(product['name'])"> ❌
^^^^^^ ^^^^^^
<input v-model="price[product['price']]" #change="calculateCost(product['name'])"> ❌
^^^^^^^ ^^^^^^
<input v-model="unit[product['name']]" #change="calculateCost(product['name'])"> ✅
<input v-model="price[product['name']]" #change="calculateCost(product['name'])"> ✅
Setting the keys to product['name'] ensures the correct lookup for unit and price in calculateCost(). Since the user could enter invalid values (non-numeric strings) or omit a value, it's possible to get NaN, so it would be a good idea to add a NaN-check in calculateCost():
calculateCost(item) {
const unit = Number(this.unit[item]);
const price = Number(this.price[item]);
if (Number.isNaN(unit) || Number.isNaN(price)) return;
this.cost[item] = unit * price;
},
Also, you probably want the cost to react to user input, so switch from #change to #input:
<input v-model="unit[product['name']]" #input="calculateCost(product['name'])">
<input v-model="price[product['name']]" #input="calculateCost(product['name'])">
demo

How to make only one element activate vue js?

How to make only one element activate vue js?
I have 3 drop-down lists, 3 are activated at once, how do I make sure that only one is activated?
As far as I understand, this needs to be done through a loop, but this framework is not given to me
<tr class="inputs-table">
<td>Type object: </td>
<td>
<div class="select">
<div class="select-header form-control" v-on:click="AddForm">
<span class="select__current">Please select one option</span>
<span class="select__off">х</span>
</div>
<addForm v-if="addedForm" />
</div>
</td>
</tr>
<tr class="inputs-table">
<td>Type business-model: </td>
<td>
<div class="select">
<div class="select-header form-control" v-on:click="AddForm">
<span class="select__current">Please select one option</span>
<span class="select__off">х</span>
</div>
<addForm v-if="addedForm"/>
</div>
</td>
</tr>
import addForm from './modal/addForm.vue';
export default {
name: 'Index',
data() {
return {
addedForm: false
}
},
methods: {
AddForm(){
this.addedForm = true;
},
closeForm() {
this.$parent.addedForm = false;
}
},
components: {
addForm,
}
}
with your question and the given screenshot on the comment section, it seems you implement dropdown list in your addForm component and when you click on <div class="select-header form-control" v-on:click="AddForm"> in "type object" or "Type business-model" component will expand the addForm component and your problem is when you click on one header both addForm components are visible.
In that case, there may be several methods to fix this. One easy way to add numbering to each component and only activate the addForm if number equals.
<tr class="inputs-table">
<td>Type object: </td>
<td>
<div class="select">
<div class="select-header form-control" v-on:click="AddForm(1)">
<span class="select__current">Please select one option</span>
<span class="select__off">х</span>
</div>
<addForm v-if="addedForm === 1" />
</div>
</td>
</tr>
<tr class="inputs-table">
<td>Type business-model: </td>
<td>
<div class="select">
<div class="select-header form-control" v-on:click="AddForm(2)">
<span class="select__current">Please select one option</span>
<span class="select__off">х</span>
</div>
<addForm v-if="addedForm === 2"/>
</div>
</td>
</tr>
import addForm from './modal/addForm.vue';
export default {
name: 'Index',
data() {
return {
addedForm: 0
}
},
methods: {
AddForm(number){
this.addedForm = number;
},
closeForm() {
this.$parent.addedForm = false;
}
},
components: {
addForm,
}
}
Since you are using 3 form element this would be enough but if you are planning to use a dynamic number of components it would be great if you use for a method such as create list for each type object
items: [{id:1, title: 'Type Object'}, {id:2, title: 'Business Model'}]
Then use v-for in your tr component such as,
<tr class="inputs-table" v-for="item in items" key="item.id">
<td>{{item.title}}: </td>
<td>
<div class="select">
<div class="select-header form-control" v-on:click="AddForm(item.id)">
<span class="select__current">Please select one option</span>
<span class="select__off">х</span>
</div>
<addForm v-if="addedForm === item.id"/>
</div>
</td>
</tr>

How to insert data form into table when press a button in Vuex?

I have two components called Form.vue and Table.vue
I want the data to be transferred to the table when the user fills out the form and clicks the submit button.
I know I made a mistake in the code.
I have very little experience in vue.
Please Help me.
You can see the code from this link:
https://codesandbox.io/s/condescending-matsumoto-w7fui
Here is a working code https://codesandbox.io/embed/naughty-chatterjee-6susb?fontsize=14&hidenavigation=1&theme=dark
First things first, you MUST use mutations to modify state values. second is that you may use data method in storing form values before submitting them.
Ive modified your store like this
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
tableData: []
},
mutations: {
addUser(state, payload) {
console.log(state);
state.tableData = state.tableData.concat(payload.data);
}
},
actions: {
addUser({ commit }, payload) {
commit("addUser", payload);
}
}
});
and in you Form, you must dispatch the addUser action once the form is submitted...
<template>
<div>
<form action>
<div class="form-group">
<label for="name">نام محصول:</label>
<input type="text" class="form-control" id="name" placeholder name="name" v-model="name">
</div>
<div class="form-group">
<label for="grouping">دسته بندی:</label>
<input
type="text"
class="form-control"
id="grouping"
placeholder
name="group"
v-model="category"
>
</div>
<div class="form-group">
<label class="code">کد محصول:</label>
<input type="text" class="form-control" id="coding" placeholder name="code" v-model="code">
</div>
<div class="form-group">
<label for="price">قیمت محصول</label>
<input
type="text"
class="form-control"
id="pricing"
placeholder
name="price"
v-model="price"
>
</div>
<!-- <button type="button" class="btn btn-success send" #click="updateName, updateCategory, updateCode, updatePrice, $store.state.show = true, send">ثبت اطلاعات</button>-->
<button type="button" class="btn btn-success send" #click.prevent="addUser">ثبت اطلاعات</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
name: "",
category: "",
code: "",
price: ""
};
},
methods: {
addUser() {
this.$store.dispatch("addUser", {
data: {
name: this.name,
category: this.category,
code: this.code,
price: this.price
}
});
}
}
};
</script>
<style scoped>
</style>
lastly, you must get the state tableData and iterate the items in your table...
<template>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>کد</th>
<th>نام محصول</th>
<th>دسته</th>
<th>قیمت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
<tr v-for="item in tableData" :key="item.name">
<td>{{ item.code }}</td>
<td>{{ item.name }}</td>
<td>{{ item.category }}</td>
<td>{{ item.price }}</td>
<td>
<button type="button" class="btn btn-primary mt">ویرایش</button>
<button type="button" class="btn btn-danger mt">حذف</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: {
...mapState(["tableData"])
}
};
</script>
<style scoped>
</style>
Here is the working code https://codesandbox.io/s/goofy-grass-c1ysg
You are on right path, most part of your code is correct, please modify your Form.vue as below to dispatch actions in your store.
<template>
<div>
<form action>
<div class="form-group">
<label for="name">نام محصول:</label>
<input type="text" class="form-control" id="name" placeholder name="name" v-model="name">
</div>
<div class="form-group">
<label for="grouping">دسته بندی:</label>
<input type="text" class="form-control" id="grouping" placeholder name="group" v-model="category">
</div>
<div class="form-group">
<label class="code">کد محصول:</label>
<input type="text" class="form-control" id="coding" placeholder name="code" v-model="code">
</div>
<div class="form-group">
<label for="price">قیمت محصول</label>
<input type="text" class="form-control" id="pricing" placeholder name="price" v-model="price">
</div>
<button type="button" class="btn btn-success send" #click.prevent="addUser">ثبت اطلاعات</button>
</form>
</div>
</template>
<script>
export default {
data(){ return { name: '', category: '', code: '', price: '', } },
methods: {
addUser() {
this.$store.dispatch('updateName', this.name)
this.$store.dispatch('updateCategory', this.category)
this.$store.dispatch('updateCode', this.code)
this.$store.dispatch('updatePrice', this.price)
},
}
};
</script>
<style scoped>
</style>
You are getting the values of form individually from store and rendering it to the input. This causes the problem that you cannot add rows to the table.
Have your store like
state: {
formData: {
name: "",
category: "",
code: "",
price: ""
},
show: false
},
Form.vue script
export default {
data() {
return { formData: this.$store.getters.formData }
},
// rest of the code
}
Change the template v-model bindings in the form to formData.name or formData.category.
So now the addUser method will have the following.
this.$store.commit("formData", this.formData);
This will update the store with submitted data.
UPDATE
Form.Vue
<template>
<div>
<form>
<div class="form-group">
<label for="name">نام محصول:</label>
<input
type="text"
class="form-control"
id="name"
placeholder
name="name"
v-model="formData.name"
>
</div>
<div class="form-group">
<label for="grouping">دسته بندی:</label>
<input
type="text"
class="form-control"
id="grouping"
placeholder
name="group"
v-model="formData.category"
>
</div>
<div class="form-group">
<label class="code">کد محصول:</label>
<input
type="text"
class="form-control"
id="coding"
placeholder
name="code"
v-model="formData.code"
>
</div>
<div class="form-group">
<label for="price">قیمت محصول</label>
<input
type="text"
class="form-control"
id="pricing"
placeholder
name="price"
v-model="formData.price"
>
</div>
<!-- <button type="button" class="btn btn-success send" #click="updateName, updateCategory, updateCode, updatePrice, $store.state.show = true, send">ثبت اطلاعات</button>-->
<button
type="submit"
class="btn btn-success send"
#click.prevent="addUser"
>submit</button>
</form>
{{this.$store.getters.fdata}}
</div>
</template>
<script>
export default {
data(){
return { formData: {} }
},
methods: {
addUser() {
this.$store.commit('updateFdata', {...this.formData})
}
}
};
</script>
Table.vue
<template>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>کد</th>
<th>نام محصول</th>
<th>دسته</th>
<th>قیمت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
<tr v-for="(fdata, index) in fData" :key=index>
<td>{{ fdata.code }}</td>
<td>{{ fdata.name }}</td>
<td>{{ fdata.category }}</td>
<td>{{ fdata.price }}</td>
<td>
<button type="button" class="btn btn-primary mt">ویرایش</button>
<button type="button" class="btn btn-danger mt">حذف</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
computed: mapGetters(['fData'])
}
</script>
Store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
formData: {
name: "",
category: "",
code: "",
price: ""
},
show: false,
fData: []
},
getters: {
fData: state => state.fData
},
mutations: {
updateFdata: (state, payload) => {
state.fData = [ ...state.fData, payload]
}
}
});

Disable/Enable input for each item from a loop VueJS

I try to enable/disable each input from a loop. The problem is that my method it works just after refresh. After I modify something in code and save, then the input works.
<tr v-for="(item, index) in customer_names" :key="item.id">
<td>
<input :disabled="!item.disabled"
v-model="item.name"
type="text"
</td>
</tr>
<div class="edit_mode"
:class="{'display-none':!item.disabled}">
<i class="fa fa-save" #click="disableInput(index)" aria-hidden="true"></i>
</div>
<div class="edit_mode"
:class="{'display-none':item.disabled}">
<i class="fa fa-edit" #click="enableInput(index)" aria-hidden="true"></i>
</div>
props:['customer_names'],
data(){
return{
disabled: [],
}
}
enableInput(index){
console.log('enableInput',this.customer_names[index].disabled);
this.customer_names[index].disabled = false;
},
disableInput(index){
console.log('disabeInput',this.customer_names[index].disabled);
this.customer_names[index].disabled = true;
}
I didn't fully understand your problem. I deduced that you might want to enable or disable the text fields that you create from the data provided. If this is still not what you meant, correct your question by pasting more source code, and explain your problem in more detail.
Vue.component("custom", {
template: "#custom-template",
props: ["customer_names"],
methods: {
enableInput(item) {
item.disabled = false;
},
disableInput(item) {
item.disabled = true;
},
toggleInput(item) {
item.disabled = !item.disabled;
}
}
});
new Vue({
data() {
return {
items: [
{ name: "fus", disabled: false },
{ name: "ro", disabled: false },
{ name: "dah", disabled: true }
]
};
}
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<custom :customer_names="items" />
</div>
<script type="text/x-template" id="custom-template">
<table cellpadding=5>
<tr>
<th>Input</th>
<th>Version 1</th>
<th>Version 2</th>
</tr>
<tr v-for="item in customer_names" :key="item.id">
<td>
<input :disabled="item.disabled" v-model="item.name" type="text" />
</td>
<td>
<button #click="item.disabled = false">E</button>
<button #click="item.disabled = true">D</button>
<button #click="item.disabled = !item.disabled">T</button>
</td>
<td>
<button #click="enableInput(item)">E</button>
<button #click="disableInput(item)">D</button>
<button #click="toggleInput(item)">T</button>
</td>
</tr>
</table>
</script>

VueJs select binding is not working

I have a categories component that looks like this:
<template>
<div>
<select v-model="categories">
<option v-for="category in categories" v-bind:value="category\.id">
{{category.name}}
</option>
</select>
</div>
</template>
<script>
export default {
data(){
return{
categories: []
}
},
created(){
this.showCategories();
},
methods: {
showCategories(){
axios.get('/app/categories').then(response => {
this.categories = response.data.categories;
});
}
}
}
</script>
I import this component inside my posts componen because I want to be able to choose a category when adding a new post, however, the categories for some reason do not show.
If it helps, my posts component looks like this:
<template>
<div class="container">
<button type="button" class="btn btn-info btn-lg" data-toggle="modal" data-target="#myModal">Add Post</button>
<div class="form-group">
<input type="text" v-model="search" class="form-control" id="search">
</div>
<categories></categories>
<table class="table">
<thead class="thead-dark">
<tr>
<th>ID</th>
<th>Title</th>
<th>Body</th>
<th>Owner</th>
<th>Category</th>
<th>Created At</th>
<th>Updated At</th>
</tr>
</thead>
<tbody>
<tr v-for="post in filteredPosts">
<td>{{ post.id }}</td>
<td>{{ post.title }}</td>
<td>{{ post.body | snippet }}</td>
<td>{{ post.user.name }}</td>
<td>{{ post.category.name }}</td>
<td>{{ post.created_at }}</td>
<td>{{ post.updated_at }}</td>
<td><button class="btn btn-primary">Edit</button></td>
<td><button class="btn btn-danger">Delete</button></td>
</tr>
</tbody>
</table>
<div class="modal fade" id="myModal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Modal Header</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label>Title</label>
<input type="text" v-model="post.title" class="form-control">
</div>
<div class="form-group">
<label>Body</label>
<textarea v-model="post.body" class="form-control"></textarea>
</div>
</div>
<div class="modal-footer">
<button #click.prevent="addPost" type="button" class="btn btn-primary" data-dismiss="modal">Submit</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return{
posts: [],
post: {
title: "",
body: ""
},
search: ""
//categories: []
}
},
created(){
this.showPosts();
//this.showCategories();
},
methods: {
/*showCategories(){
axios.get('/app/categories').then(response => {
this.categories = response.data.categories;
});
},*/
showPosts(){
axios.get('/app/posts').then(response => {
this.posts = response.data.posts;
});
},
addPost(){
axios.post('/app/posts', {
title: this.post.title,
body: this.post.body,
})
.then(response => {
this.showPosts();
//console.log('Added');
})
.catch(function (error) {
console.log(error);
});
}
},
computed: {
filteredPosts: function(){
return this.posts.filter((post) => {
return post.title.match(this.search);
});
}
}
}
</script>
OBS: If I use an li tag like this inside the categories component I manage to see all the categories:
<li v-for="category in categories">{{category.name}}</li>
How can I show my categories inside the posts component using the select binding?
In your Select element you are binding v-model to array categories instead bind another variable selectedCategory in v-model like this
<select v-model="selectedCategory">
<option v-for="category in categories" v-bind:value="category.id">
{{category.name}}
</option>
</select>
<script>
export default {
data(){
return{
selectedCategory:null,
categories: []
}
},
created(){
this.showCategories();
},
methods: {
showCategories(){
axios.get('/app/categories').then(response => {
this.categories = response.data.categories;
});
}
}
}
</script>