vue 2.x
i have a file array, each item contains a file input as well as other text input, user can click + icon to add a item and have to upload one file or delete one item.
it looks like following:
however, if i add two items and each item has a file, then i delete the first item, the file input(original second item) will be cleared too like :
that is :
Level A a.pdf
Level B b.pdf
if i delete first, then it changed to
Level B no file selected
why? how to solve it?
my vue code is:
<tr v-for=" (a, index) in eb.attachmentList" v-bind:key="index">
<td>
<select v-model="a.typeId" required>
<option value="1">Level A</option>
<option value="13">Level B</option>
<option value="8">Level C</option>
</select>
</td>
<td><input type="text" v-model="a.description" /></td>
<td>
<input v-if="!a.eaId" type="file" v-on:change="selectFile(index, $event)" ref="f" required />
<a v-if="a.eaId" href="#" v-on:click="downloadFile(a)">{{a.fileName}}</a>
</td>
<td><i class="far fa-trash-alt fa-lg" v-on:click="delAttachment(a, index)" style="color: red"></i>
</td>
</tr>
data () {
return {
eb: {
'attachmentList': []
}
}
},
methods: {
addAttachment() {
var a = {'typeId': '', 'description': '', 'file': ''};
this.eb.attachmentList.push(a);
},
selectFile( index, e ){
this.eb.attachmentList[index].file = e.target.files[0];
},
delAttachment( a, index ) {
this.eb.attachmentList.splice(index, 1);
//and delete this attachment from database
}
}
Setting the key as your index causes problems when updating arrays.
Instead generate a unique id for your items:
var a = {'typeId': '', id: new Date().valueOf() };
Then set key to a.id
working example: https://jsfiddle.net/ellisdod/tg2yr9L8/
Related
I have an input field and a button (when clicked on displays a dropdown with few items) when selecting the items it has to be shown on the first input field. Similarly when clicking on the 2nd button where the dropdown is shown the selected value is shown in the 2nd input field. This entire runs in a for loop , which is where I am facing the problem.
<tr v-for="items in itemList">
<td valign="top"> {{items}} </td>
<td align="left" nowrap>
<input v-model="itemCode" type="text" :id="'item_code_'+items"
#input="handleInput"
size="20" maxlength="27"
autocomplete="off">
<br/>
</td>
<td align="left" nowrap>
<a id="myDropdown" class="dropdown" style="text-decoration:none;font-
size:10pt; padding-left: 10px;padding-right: 10px;"
#click="loadFavs()"
href="javascript:void(0)" title="Click to choose an item from your
favorites">
<img hspace="3" alt="Favorites" src="/images/icons/LoadFav.png"
height="16" width="16"
onmousemove="this.style.cursor='pointer'"
:id="'bd_fav_image_' + items" title="Click to choose an item from
your favorites">
<select class="dropdown-content" v-if="showFav" name="BOMList"
:id="'bd_list_'+items" style="font-size:10pt;width: 100%" v-
model="selected" #change="selectingFav(items)">
<option value=""></option>
<option v-for="(fav,index) in favList" :id="index" v-
bind:value="fav" :key="fav" v-bind:index="index">{{fav}}
{{index}}</option>
</select>
</a>
</td>
<td valign="top" nowrap >
<input type="Text"
:id="'bd_qty_ '+ index"
value="" size="2"
inputmode="numeric"
maxlength="">
</td>
</tr>
favList--> this list holds a list of items , For eg:- A,B,C,D
When I select A it has to be shown in the input field.
selectingFav: function(value) {
console.log("Inside the selectingFav..." + this.selected + "value is ." +value);
setTheValue(value);
}
function setTheValue(val){
console.log("Inside the setThevlaue");
if (val === 1 ){
console.log("inside the if");
$j('#item_code_1').val(this.selected);
console.log("inside the if witht the value " + $j('#item_code_1').val());
}
Tried setting the value based on the id of the input field but nothing is showing up.
If I set the v-model to the input field then all the 3 fields will be showing up the same value.
Can someone please let me know what is the issue. Hope these details are sufficient.
a) v-model is internally implemented as:
<input v-model="myval">
<!-- is --!>
<input :model-value="myval" #update:model-value="v => myval = v">
so you can freely define your own function
<input v-for="obj, ind of whatever" #update:model-value="v => myfn(v, obj, ind)">
b) same as you have an array you v-for on you may make a parallel array
// you get the idea
data: () => ({ imputs: entries.map(e => 0) })
<div v-for="entry, ind of imputs">
<Whatever :entry="entry"/>
<imput v-model="imputs[ind]">
</div>
c) keep your imputs in objects, generally the best choice
// you get the idea
data: () => ({ imputs: entries.map(e => ({ entry: e, input: 0 })) })
// or
computed: {pairs(){ return this.entries.map(e => ({ entry: e, input: 0 })) }}
<div v-for="item of imputs">
<Whatever :entry="item.entry"/>
<imput v-model="item.input">
</div>
Here is how you can achieve that.
data() {
return {
itemList: [
{ id: 1 , value: '' },
{ id: 2, value: '' },
{ id: 3, value: '' }
]
}
},
methods:{
selectingFav: function(value) {
// value holds the index
if (value === 1 )
this.itemList[0].value = this.selected;
else if(value === 2 )
this.itemList[1].value = this.selected;
else
this.itemList[2].value = this.selected;
}
}
}
In HTML template section
<tr v-for="(items,i) in itemList">
<td valign="top"> {{items.id}} </td>
<td align="left" nowrap>
<input v-model="items.value" type="text" :id="'item_code_'+items"
#input="handleInput" size="20" maxlength="27" autocomplete="off">
<br/>
</td>
Iam using vee-validate plugin for validation. In my form, there is a select field in the table. Rows will be added dynamically in the table. I don't want to select the same select(Description column) option again and again Image. Hence I want to throw an error like "Selected description already exists in a table" this using vee-validate. Kindly help me to solve this.
Here is my code:
<template>
<div>
<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 class="form-control" v-model="row.billChgDesc" v-validate="'required|check'" :name="'billChgDesc' + index" data-vv-as="Description" #change="checkRepetation">
<option v-for="option in billChgDescOpt" v-bind:value="option.value"
:key="option.value"> {{ option.text }}
</option>
</select>
<span v-show=" errors.has('billChgDesc' + index)" class="is-danger">{{ errors.first('billChgDesc' + index) }}</span>
</td>
<td>
<input class="form-control text-right" type="text" v-model="row.charges" data-type="currency" v-validate="'required'" :name="'charges' + index" data-vv-as="Charges" >
<span v-show=" errors.has('charges' + index)" class="is-danger">{{ errors.first('charges' + index) }}</span>
<td>
<input class="form-control text-right" :value="row.qty * row.charges" 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">TOTAL</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>
</div>
</template>
<script>
import Vue from 'vue'
import accounting from 'accounting'
export default {
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,
selectArr:[]
}
},
methods: {
addRow: function (index) {
try {
this.rows.splice(index + 1, 0, {});
} catch(e)
{
console.log(e);
}
},
removeRow: function (index) {
this.rows.splice(index, 1);
},
checkRepetation:function(){
this.$validator.extend('check', {
getMessage: field => '* Slected ' + field + ' already exists',
validate: function(value){
selectArr.push(value);
}
})
}
}
}
</script>
<style lang="scss" scoped>
.is-danger{
color: RED;
}
</style>
Thanks in advance.
You're on the right track, but a couple changes need to be made. When you call this.$validator.extend, that only needs to be done once - when your component is created. It attaches the check method to the validator, so then every time you have the attribute v-validate="'required|check'" in your HTML, it will run that check method.
In your check validator, you need to answer the question "is this value already selected". The answer is to go through the this.rows and see if any of them have the same billChgDesc property. Because this is in Vue, by the time the validator gets run, the row in question already does have that value, so you want to check if MORE than one row have that value. So, something like this:
mounted() {
var self = this;
this.$validator.extend('check', {
getMessage: field => '* Selected ' + field + ' already exists',
validate: function(value){
return (self.rows.filter(function(v){
return v.billChgDesc == value;
}).length <= 1);
}
});
}
This validator returns true if only one item has the given value. I'm using the built-in filter method of Array (see docs).
You can see an example of this all working here: https://jsfiddle.net/ryleyb/f9q50wx4/1/
I have an html page, which has a table and an iterated row using v-for:
<table id="app-4">
<tr v-for="(item, index) in results" :key="item.id">
<td>#{{ item.title }}</td>
<td>
<button v-on:click="deleteItem(index)">Delete</button>
</td>
</tr>
</table>
and I have this js code.
var app4 = new Vue({
el: '#app-4',
data: {
results: []
},
methods: {
deleteItem: function (index) {
this.results.splice(index,1);
//Can I access item key and tr properties from here and the delete button
}
},
mounted() {
axios.get('api.list.url').then(response => {
this.results = response.data;
})
}
});
In the deleteItem function, Can I access item key and tr properties and append text to the item delete button.
The traditional Vue approach would probably be to use references
<table id="app-4">
<tr v-for="(item, index) in results" :key="item.id" ref="rows">
<td>#{{ item.title }}</td>
<td>
<button v-on:click="deleteItem(index)" ref="deleteButtons>
Delete
</button>
</td>
</tr>
</table>
And in the code
deleteItem: function (index) {
this.results.splice(index,1);
//Can I access item key and tr properties from here?
// Yes, e.g. to get the text content of the first cell
const text = this.$refs.rows[index].cells[0].textContent.trim();
// And add it to the delete button text
this.$refs.deleteButtons[index].textContent += " " + text;
}
Of course, that example is a bit nonsensical since you know the item's title, but the principle works for other properties of the text row (e.g. attributes, computed styles, classes, etc.)
I have a small issue with my vue template. The code is the following :
<template>
<div class="panel panel-default"
v-bind:id="'panel_'+noeud.id">
<div class="panel-heading">{{noeud.name}}</div>
<div class="panel-body">
<table class="table">
<thead>
<tr>
<th>Noeud</th>
<th>Poid</th>
</tr>
</thead>
<tbody>
<tr
v-for="noeud_poids in weightSorted"
v-if="noeud_poids.macro_zonning_noeud_id_2 != noeud.id"
is="macrozonningproximitenoeudpoids"
:noeud_poids="noeud_poids"
:noeud="noeud"
:noeuds="noeuds"
:delete_callback="delete_final"
:change_callback="update_line">
</tr>
<tr>
<td>
<select v-model="new_noeud">
<option value=""></option>
<option v-for="one_noeud in noeuds "
v-bind:value="one_noeud.id">{{one_noeud.name}}</option>
</select>
</td>
<td>
<input type="text" v-model="new_weight">
</td>
<td>
<input type="button" class="btn btn-primary" #click="addNoeudProximite" value="Ajouter"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
props: ['pnoeud', 'pnoeuds'],
data: function(){
return {
points: 0,
points_restants: 100,
new_weight:0,
new_noeud:0,
noeud:this.pnoeud,
noeuds:this.pnoeuds,
weightSorted:this.pnoeud.weightSorted
}
},
mounted() {
},
methods:{
delete_final(macro_zonning_noeud_id_2){
axios.delete("/macrozonning/proximite/",{
params:{
macro_zonning_noeud_id_2:macro_zonning_noeud_id_2,
macro_zonning_noeud_id_1:this.noeud.id
}
}).then((res) => {
Vue.delete(this.weightSorted, String(macro_zonning_noeud_id_2));
})
},
update_line(nb_points){
this.points_restants = this.points_restants - nb_points;
this.points = this.points + nb_points;
},
addNoeudProximite(){
axios.put('/macrozonning/proximite/', {
'macro_zonning_noeud_id_1': this.noeud.id,
'macro_zonning_noeud_id_2': this.new_noeud,
'weight': this.new_weight
}).then((res) => {
Vue.set(this.weightSorted, String(this.new_noeud), res.data);
});
}
}
}
</script>
When the function delete_final is executed on the last item of my list, the view is correctly rerendered as the last item of my list is removed. But when I try to remove the first item of my list then the view rerenders but the the last item has been removed. When I check the Vue object in devtools, it does not reflect the new view, but it reflects the action taken (my first item has been removed).
If you have any idea where this problem comes from it would be awesome.
Thanks a lot community
Use a key attribute on the element you are rendering with v-for so that vue can exactly identify VNodes when diffing the new list of nodes against the old list. See key attribute
<tr> v-for="noeud_poids in weightSorted" :key="noeud_poids.id" </tr>
I am using a modal form to add new details to the row of a table. After adding details, I’m just adding edit and delete buttons at the end of it. Now here delete button is working fine. How to edit a row of a table by popping replicate of form modal by clicking “edit” button in a row.
Here’s my code:
<div class="layout-padding">
<div
class="item item-link"
v-for="modal in types"
#click="$refs[modal.ref].open()"
>
<i class="item-primary">add</i>
<div class="item-content has-secondary">
<div>{{modal.label}}</div>
</div>
</div>
</div>
<q-modal ref="maximizedModal" class="minimized" :content-css="{padding: '50px'}">
<div class="main">
<label>Enter Your Name:</label>
<input id="name" name="name" type="text" placeholder="Enter your Name" v-model="YourName">
<br>
<label>I am:</label>
<input type="radio" id="Male" value="male" v-model="picked">
Male
<input type="radio" id="Female" value="female" v-model="picked">
Female
<br>
<div class="button">
<button class="red" #click="$refs.maximizedModal.close()">Close</button>
<button class="blue" v-on:click="sub" #click="$refs.maximizedModal.close()">Submit</button>
</div>
</div>
</q-modal>
<table>
<thead>
<th>Name</th>
<th>Gender</th> </thead>
<tbody class="result">
<tr v-for="(h, index) in final">
<td v-for="(value, key, index) in h">
{{ value }}
</td>
<td><button id="edit" class="green edit" v-for="modal in types"
#click="ed(h, index);$refs[modal.ref].open()" type="submit">EDIT</button></td>
<td><button id="delete" class="red delete" v-on:click="del(index)" type="submit">Delete</button></td>
</tr>
</tbody>
</table>
And my script is:
export default {
data () {
return {YourName: '',
details: [],
final: [],
types: [
{
label: 'Add Details',
ref: 'maximizedModal'
}
],
position: 'bottom'
}
},
methods: {
sub: function () {
this.details = {
'name': this.YourName,
'gender': this.picked,
}
this.ed()
if (index === '[object MouseEvent]') {
this.final.push(this.details)
}
if (index > -1) {
this.final.splice(index, 1, this.details)
}
else {
alert('else')
alert(JSON.stringify(this.details))
this.final.push(this.details)
}
},
del: function (index) {
this.$delete(this.final, index)
},
ed: function (details, index) {
return index
}
}
}
If edit button is clicked, the same row should be edited. I don’t know how to proceed further. Please, guide me.
Using the 'splice' can able to modify the given array of object.
Can simply include this inside an 'if' loop:
this.final.splice(this.indi, 1, this.details)