Vue 3 problems with value input - vue.js

I have two components, the first one is a row insertion table and the second one contains the rows
I have a problem with the inputs, everything works fine except that when I delete a line in the middle of the table the values of the other inputs are reset,
the parent component :
<script>
import editortable from "./editortable.vue";
export default {
data: () => {
return {
elements: [{ id: 0, ref: "1", equation: "2" }],
nextid: "1",
};
},
methods: {
removrow(index) {
this.elements.splice(index, 1);
},
add() {
this.elements.push({
id: this.nextid++,
ref: "",
});
},
},
components: {
editortable,
},
};
</script>
<tbody>
<tr
is="vue:editortable"
v-for="(element, index) in elements"
:iden="element.id"
:referen="element.ref"
:eq="element.equation"
:key="index"
#delete="removrow(index)"
></tr>
</tbody>
<button type=" button" class="btn btn-primary" #click="add">
Ajouter une ligne
</button>
** component :**
<script>
export default {
props: ["iden", "referen", "eq"],
emits: ["delete"],
methods: {
deleterow() {
this.$emit("delete");
},
},
};
</script>
<template>
<tr>
<td>
<input class="form-control" type="ipunt" :value="iden" />
</td>
<td><input class="form-control" type="ipunt" :value="referen" /></td>
<td><input class="form-control" type="ipunt" :value="eq" disabled /></td>
<td>
<button type="button" class="btn btn-danger" #click="deleterow">
delete
</button>
</td>
</tr>
</template>

There is 1 obvious issue is you shouldn't use index as the key in your case.
Change it to use the id.
:key="element.id"
And in removrow(id), using id find the index first and then remove it should be ok.
removrow(id) {
const index = this.elements.findIndex(ele => ele.id === id);
this.elements.splice(index, 1);
}

Related

problems with having a vue component in for loop

Please first take a look at following code:
<tr v-for="item in items">
<td> ... </td>
<td> ... </td>
<td> ... </td>
<td> <actions-manager :item="item" /> </td>
</tr>
As you can see I am including actions-manager component inside a for loop for each item.
In actions-manager I have following code :
<ul>
<li v-for="action in item.actions">
<a #click="initAction(action.name, item.id)" href="javascript:;">
{{action.name}}
</a>
<a #click="someOtherMethod()" href="javascript:;">
...
</a>
</li>
</ul>
my data:
data : function() {
return {
currentActionName : null,
currentItemId : null
}
}
my methods :
methods : {
initAction : function (actionName, itemId) {
this.currentActionName = actionName;
this.currentItemId = itemId;
},
someOtherMethod : function () {
console.log(this.currentActionName, this.currentItemId);
}
}
and now initAction method works completely fine for the first table row and sets currentActionName and currentItemId properly, but not for other rows.
for example :
when I call someOtherMethod after initAction in first table row I can access currentActionName and currentItemId and it works fine for first row, but for other rows initAction is not setting values for currentActionName and currentItemId.
any ideas why?
According to the structure of your code I guess these two components are similar to what you want:
parent component that has tr tags:
<template>
<section>
<tr v-for="item in items">
<td> <actions-manager :item="item" />
item1
</td>
<td> <actions-manager :item="item" />
item2
</td>
<td> <actions-manager :item="item" />
item3
</td>
<td> <actions-manager :item="item" />
item4
</td>
<td> <actions-manager :item="item" />
item5
</td>
</tr>
</section>
</template>
<script>
import actionsManager from "#/components/actionsManager.vue";
export default {
data() {
return {
items: [
{
actions: {
action1: {
name: "first item",
},
action2: {
name: "second item",
},
action3: {
name: "third item",
}
},
id: 1
},
{
actions: {
action4: {
name: "fourth item",
},
action5: {
name: "five item",
},
action6: {
name: "six item",
}
},
id: 2
},
]
}
},
components: {
actionsManager
}
}
</script>
actionsManager component:
<template>
<ul>
<li v-for="action in item.actions">
<a #click="initAction(action.name, item.id)" href="javascript:;">
{{action.name}}
</a>
<a #click="someOtherMethod()" href="javascript:;">
...
</a>
</li>
</ul>
</template>
<script>
export default {
props: ["item"],
data : function() {
return {
currentActionName : null,
currentItemId : null
}
},
methods : {
initAction : function (actionName, itemId) {
this.currentActionName = actionName;
this.currentItemId = itemId;
},
someOtherMethod : function () {
console.log(this.currentActionName, this.currentItemId);
}
}
}
</script>
And that works fine in my vue-cli development environment! I could not understand what is the problem. You can test those two component and see that console logs data correctly.

How to close a dropdown list when clicking anywhere

How do I close the dropdown that opens when I click anywhere?
This code:
<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>
</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>
</div>
<addForm v-if="addedForm === 2" />
</div>
</td>
</tr>
export default {
name: 'Index',
data() {
return {
addedForm: 0,
}
},
methods: {
AddForm(number) {
this.addedForm = number;
},
closeForm() {
this.addedForm = false;
}
},
components: {
addForm,
}
}
Drop-list:
What can I try next?
You can make use if window.onClick and see whether or not it matches you'r element. If it doesn't then close it; call the function that checks clicks at mounted and beforeDestroy or breforeRouteLeave.
E.g:
mounted(){
this.hideNav()
},
methods: {
hideNav() {
window.onclick = function(event) {
if (!event.target.matches('.select__body')) {
this.closeForm()
}
}
}
},
beforeRouteLeave() {
this.hideNav()
}
<template>
<div
#click="dropdownIsActive = !dropdownIsActive"
ref="dropdown"
>
</div>
</template>
<script>
export default {
data: () => ({
dropdownIsActive: false
}),
created () {
window.addEventListener('click', this.closeDropdown)
},
methods: {
closeDropdown (e) {
if (!this.$refs.dropdown.contains(e.target)) {
this.dropdownIsActive = false
}
}
},
beforeDestroy () {
window.removeEventListener('click', this.closeDropdown)
}
}
</script>

how to send input data value from child component data object to parent?

I am trying to send input data variables from child component form to parent component through dataobject 'data()'. I have seen vuejs update parent data from child component article and tried to do it but, i am unable to $emit captured dataobject through an event. can you please help me out.
Parent component:
<script>
import inputform from '../components/form.vue';
import axios from 'axios';
export default {
name: 'helloword',
data() {
return {
}
},
components: {
inputform
},
methods: {
submit() {
const path = 'http://127.0.0.1:5000';
axios.post(path, {
name: inputform.data()
})
.then(() => {
const result = 'Sucess!';
console.log(result);
})
.catch((error) => {
console.log(error);
})
}
}
}
</script>
Child component:
<template>
<div>
<table>
<thead>
<th>Name</th>
<th>Email</th>
<th>Age</th>
</thead>
<tr>
<td><input type="text" id="name" v-model="details.name" #focusout="inputdata"></td>
<td><input type="text" id="name1" v-model="details.name1" #focusout="inputdata" ></td>
<td><input type="number" id="age" v-model="details.age" #focusout="inputdata" ></td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: "inputform",
data() {
return {
details: {
name: '',
name1: '',
age: ''
}
}
},
methods: {
inputdata() {
this.$emit("input_data", this.details)
}
}
}
</script>
<style scoped>
</style>
So, looking for help with emitting variable data from child compnent to parent and perform submit operation to API using axios from parent component. If there is any other better way please let me know. Thanks.
When attaching a v-model you don't need a v-on. You could also look to capture details into a single object like so and then pass it as part of the event emitted.
Child component
<template>
<div>
<table>
<thead>
<th>Name</th>
<th>Email</th>
<th>Age</th>
</thead>
<tr>
<td>
<input type="text" id="name" v-model="details.name">
</td>
<td>
<input type="email" id="email" v-model="details.email">
</td>
<td>
<input type="number" id="age" v-model="details.age">
</td>
<td>
<button #click="inputdata">Submit</button>
</td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: "inputform",
data() {
return {
details: {
name: "",
email: "",
age: ""
}
};
},
methods: {
inputdata() {
console.log(this.details);
this.$emit("handledata", this.details);
}
}
};
</script>
Parent component
<template>
<div id="app">
<HelloWorld v-on:handledata="handleInput"/>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
HelloWorld
},
methods: {
handleInput(data) {
// object emitted from the child component
console.log({ data });
}
}
};
</script>
well first you should pass max two params to $emit method here's the docs: https://v2.vuejs.org/v2/api/#vm-emit and second is the v-on: before v-models is extra.
so the solution you can pass this data in one object instead of three data so the code will be like this:
data() {
return {
name: '',
email: '',
age: '',
}
},
methods: {
inputdata() {
this.$emit("input", {
name: this.name,
email: this.email,
age: this.age
})
}
}
or my prefer option put all in a form data like this
<template>
<div>
<table>
<thead>
<th>Name</th>
<th>Email</th>
<th>Age</th>
</thead>
<tr>
<td><input type="text" id="name" v-model="form.name"></td>
<td><input type="email" id="email" v-model="form.email"></td>
<td><input type="number" id="age" v-model="form.age"></td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: "inputform",
data() {
return {
form: {
name: '',
email: '',
age: '',
}
}
},
methods: {
inputdata() {
this.$emit("input", this.form)
}
}
}
</script>

dynamically call an object property in a v-for loop

so i ran into a problem again,
i want to make a table component where you can send a array to the component, and it will render a table for you
we set it up like this
<template>
<section class="container">
<Apptable :search="true" :loader="true" title="User data" :data="users"/>
</section>
</template>
<script>
import Apptable from "~/components/table.vue";
export default {
components: {
Apptable
},
data() {
return {
users: [
{
id: 1,
name: "Lars",
Adres: "hondenstraat 21",
phone: "06555965"
},
{
id: 1,
name: "John",
Adres: "verwelstraat 35",
phone: "06555965"
}
]
};
}
};
</script>
i send data to the component and loop it from there like this
<template>
<section class="container">
<h2 v-if="title">{{title}}</h2>
<input v-if="search" class="search" placeholder="Search">
<button v-if="loader" class="update" #click="dialog = true">Update</button>
<table class="table">
<thead>
<tr class="tableheader">
<th v-for="(item, index) in Object.keys(data[0])" :key="index">{{item}}</th>
</tr>
</thead>
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, index) in Object.keys(data[index])" :key="index">{{//TODO: I WANT TO SELECT THE ITEM.DYNAMIC PROPERTY}}</td>
</tr>
</tbody>
</table>
<loader v-if="loader" :trigger="dialog"/>
</section>
</template>
<script>
import loader from "~/components/loader.vue";
export default {
components: {
loader
},
data() {
return {
dialog: false
};
},
watch: {
dialog(val) {
if (!val) return;
setTimeout(() => (this.dialog = false), 1500);
}
},
props: {
data: {
type: Array,
required: true
},
title: {
type: String,
required: false,
default: false
},
loader: {
type: Boolean,
required: false,
default: false
},
search: {
required: false,
type: Boolean,
default: true
}
}
};
</script>
so if you look at the table. were i left the todo, if i put in the {{item}} in the todo place. i will get this in my column
enter image description here
but i want to select the key of the object dynamically. but if i put {{item.name}} in the todo place it will not select the key dynamically.
so the point is that i want to dynamically call a property from the object in the v-for so the columns will get the data in the cells.
You should use item[name] instead of item.name
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, nIndex) in Object.keys(data[index])" :key="nIndex">
{{ item[name] }}
</td>
</tr>
</tbody>

Vuejs2- How to call a filter function from a method

I am using "moneyFormat" filter for formatting the currency value. It's formatting the values which is defined already. I want to format the dynamic values. Hence I have called the filter function through a method called "displayValue", but I am getting error
and the given input field also not updated.
Here is my code :
<template>
<b-card>
<div class="panel-body" id="app">
<table class="table table-hover">
<thead>
<tr>
<th style="width: 20px;">No.</th>
<th style="width: 330px;">Description</th>
<th style="width: 130px;" class="text-right">Charges</th>
<th style="width: 130px;">Total</th>
<th style="width: 130px;"></th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in rows" :key="row.qty">
<td>
{{ index +1 }}
</td>
<td>
<select name="" id="" class="form-control" v-model="row.billChgDesc">
<option v-for="option in billChgDescOpt" v-bind:value="option.value"
:key="option.value"> {{ option.text }}
</option>
</select>
</td>
<td>
<input #input="displayValue" class="form-control text-right" type="text" v-model="row.charges" data-type="currency" v-validate="'required'" :name="'charges' + index">
<span v-show="vErrors.has('charges' + index)" class="is-danger">{{ vErrors.first('charges' + index) }}</span>
<td>
<input class="form-control text-right" :value="row.qty * row.charges | moneyFormat" number readonly />
<input type="hidden" :value="row.qty * row.charges * row.tax / 100" number/>
</td>
<td>
<button class="btn btn-primary btn-sm" #click="addRow(index)"><i class="fa fa-plus"></i></button>
<button class="btn btn-danger btn-sm" #click="removeRow(index)"><i class="fa fa-minus"></i></button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3" class="text-right">DELIVERY</td>
<td colspan="1" class="text-right"><input class="form-control text-right" v-model="delivery" number/></td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</b-card>
</template>
<script>
import Vue from 'vue'
import accounting from 'accounting'
export default {
filters:{
moneyFormat: function (val){
if (val > 0) {
return accounting.formatMoney(val, " ₹ ", 2, ",", ".");
}
}
},
data: function () {
return {
billChgDescOpt: [
{ value: '', text: 'Select' },
{ value: 'M', text: 'Maintenance Fee'},
{ value: 'W', text: 'Water Charges'},
{ value: 'P', text: 'Penalty Fee'},
],
rows: [
{qty: 5, billChgDesc: '', charges: 55.20, tax: 10},
{qty: 19, billChgDesc: '', charges: 1255.20, tax: 20},
],
grandtotal: 0,
delivery: 40
}
},
computed: {
total: function () {
var t = 0;
$.each(this.rows, function (i, e) {
t += accounting.unformat(e.total, ",");
});
return t;
},
taxtotal: function () {
var tt = 0;
$.each(this.rows, function (i, e) {
tt += accounting.unformat(e.tax_amount, ",");
});
return tt;
}
},
methods: {
addRow: function (index) {
try {
this.rows.splice(index + 1, 0, {});
} catch(e)
{
console.log(e);
}
},
removeRow: function (index) {
this.rows.splice(index, 1);
},
displayValue:function (e) {
var value = e.target.value
var a = this.filters.moneyFormat(value);
return a;
}
}
}
</script>
<style lang="scss" scoped>
.is-danger{
color: RED;
}
</style>
You could use:
this.$options.filters.moneyFormat(value)
Check: https://v2.vuejs.org/v2/api/#vm-options
For global filters, first set:
Vue.prototype.$filters = Vue.options.filters
And then:
this.$filters.foo
Edit:
Looking closer your code, you are not using the filter as a Vue filter and only calling from one point (a method) instead of calling inline from HTML, maybe it's better that the method itself returns the value of the input, like:
displayValue: function (e) {
var val = e.target.value
if (val > 0) {
return accounting.formatMoney(val, " ₹ ", 2, ",", ".");
}
}
Did it work? Or the same error is shown? If yes, can you paste the error?
Hope it helps!
As it's been said, if you want to use the filter, you need to do this.$options.filters.moneyFormat(value)
What you're trying to achieve it's rendered the moneyFormat inside an input and the value displayed is the v-model. It's this one you have to format.
So you can initialize a new data property filled with each row.charges formatted on mounted:
data: function () {
return {
rows: [
//...
],
currentCharges: []
}
},
mounted() {
this.rows.forEach(row => {
let formattedCharges = this.$options.filters.moneyFormat(row.charges)
this.currentCharges.push(formattedCharges)
})
},
and use this data to fulfill your inputs:
<tr v-for="(row, index) in rows">
<td>
<input v-model="currentCharges[index]" #input="displayValue($event, index)">
<td>
To keep the current row.charges updated, reassign it when the v-model updates:
methods: {
displayValue:function (e, index) {
// the target value is a string like this " ₹ 55.20"
// split it and convert the last element to Float type
let arrValue = e.target.value.split(" ")
let parseValue = parseFloat(arrValue[arrValue.length -1])
// reassign the row.charges with the new float value
this.rows[index].charges = parseValue
}
},