Dynamic attribute selection in Vue - previous selection is being made blank - javascript

The attributes data comes from an API and the attribute names are dynamic, however to make this example simpler i have put an example with an object which has Colour and Size. I was primarily trying to map data to an object selectedAttrObj - which has no problems, however when the second sets of attributes are selected (Size), the first one (Colour) is becoming blank. This must be due to the fact that the first v-model="selected" is being overwritten when second set is selected. This is a visual experience, and how I can make sure the first select stays with the selected option. Please do not try to hardcode as there could be countless number of attributes, so it needs to be dynamic (hence the reason for using selected for all attributes). If there is a better and simpler way of mapping the selected data to selectedAttrObj to avoid blanking out previous selections, please fire away! Thanks
function callMe(){
var vm = new Vue({
el : '#root',
data : {
attributes : {
"Colour": ["red", "black", "purple"],
"Size": ["8.0", "8.5", "9.0", "9.5", "10.0"]},
selectedAttrObj: {},
selected: ""
},
methods: {
selected_attributes(name, value) {
this.selectedAttrObj[name] = value
console.log(this.selectedAttrObj)
}
}
})
}
callMe();
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.11/dist/vue.js"></script>
<div id='root'>
<table>
<tr>
<th v-for="(item, key, index) in attributes "> {{ key }} </th>
</tr>
<tr>
<td v-for="(items, key, index) in attributes">
<select v-model="selected" #change="selected_attributes(key, selected)">
<option v-for="name in items"> {{ name }} </option>
</select>
</td>
</tr>
</table>
</div>
</div>

You can change your data variable selected to be a object and save the values based you the given key you are iterating.
Here is a snippet:
function callMe(){
var vm = new Vue({
el : '#root',
data : {
attributes : {
"Colour": ["red", "black", "purple"],
"Size": ["8.0", "8.5", "9.0", "9.5", "10.0"]},
selected: {}
}
})
}
callMe();
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id='root'>
<table>
<tr>
<th v-for="(item, key, index) in attributes "> {{ key }} </th>
</tr>
<tr>
<td v-for="(items, key, index) in attributes">
<select v-model="selected[key]">
<option v-for="name in items"> {{ name }} </option>
</select>
</td>
</tr>
</table>
</div>
</div>

Related

How to display a dropdown based on contents of the current array index in a table

I have a object array that I want to use in a table. I need a drop down select if the validvalues has a value. How to you do this so that each row of the table has different options from the array? If the validvalues is empty, it should be a div/input in the table row but it validvalues is not empty, it should be a dropdown with the validvalues as the options of the select.
As written, updateOptions() doesn't get called and an empty select shows on every row (insert sad emoji).
TS file
tags = [
{tagName: 'AppID', value: 'T400', validvalues: 'T100/T200/T300/T400/T500'},
{tagName: 'Series', value: 'SUPR', validvalues: ''},
{tagName: 'Collection', value: 'AUTO', validvalues: 'EFIT/AUTO/FLEET'},
{tagName: 'Function', value: 'Accounting', validvalues: 'Sales/Marketing/Accounting/Shop'},
{tagName: 'Contact', value: 'Jim Jones', validvalues: ''}]
validValuesArray: any = [];
updateOptions(parsingString: string) {
let tagValidValuesArray = parsingString.split('/');
}
HTML file
<table>
<tr>
<th>Tag Name</th>
<th>Value</th>
</tr>
<tr *ngFor="let tag of tags; let i=index">
<td>
<div>{{tag.tagname}}</div>
</td>
<td>
<div *ngIf="!tag.validvalues">
<input type="text" name="value" [(ngModel)]="tag.value">
</div>
<div *ngIf="tag.validvalues">
<select [(ngModel)]="tag.value" (ngChange)="updateOptions(tag.validvalues)">
<option *ngFor="let value of validValuesArray">{{value}}</option>
</select>
</div>
</td>
</tr>
</table>
If I understand you correctly you want your first table row to have a dropdown with the options:
T100
T200
...
Then your would have to have away to convert your string of validValues into an interable type. For example by calling validValues.split('/') this would convert your string into a string[] which you could then loop over.
Since you typically don't want to call methods in your template for performance reasons I would recommend to transform the validValues into an Array when you load them, or even before that in your database if possible unless there is a specific reason, why you would hold "a list of Options" as anything but a "listlike" data structure.
The solution is/was to load the validValuesArray after tagDtail array is loaded and then use the validValuesArray for the dropdown options. Like this...
for (var m = 0; m < this.tagDtails[0].tags.length; m++){
let currentTag = this.tagDtails[0].tags[m];
let parsingString = currentTag.validValues;
this.validValuesArray.push(parsingString.split('/'));
}
This will create an array or arrays that looks like this:
validValuesArray = [
["T100","T200","T300","T400","T500"],
[],
["EFIT","AUTO","FLEET"],
["Sales","Marketing","Accounting","Shop"],
[]]
then, in the html, loop through the validValuesArray
<tr *ngFor="let tag of tagDtails.tags; let i = index">
<td>
<div>{{ tag.tagName }}</div>
</td>
<td class="td-fix">
<div *ngIf="this.validValuesArray[i]==''">
<input type="text" class="form-control" name="tagValue" [(ngModel)]="tag.tagValue">
</div>
<div *ngIf="this.validValuesArray[i]!=''">
<select [(ngModel)]="tag.value">
<option *ngFor="let value of validValuesArray[i]">{{value}}</option>
</select>
</div>
</td>
</tr>

How to bind an object to HTML table in Vue.js, where its properties are dynamically created?

I am using vue.js library for front-end development.
I came a cross a scenario where my JavaScript method returns a list, which has objects, object's number of properties can change each time after method execution.
example my list can contain these type of objects in 2 different executions.
var obj = {
Name : "John",
2020-Jan: 1,
2020-Jul: 2
}var obj = {
Name: "John",
2020-Jan: 1,
2020-Jul: 2,
2021-Jan: 3,
2021-Jul: 4
}
Since Property name is dynamically changes is there any way to bind to HTML ?
<div >
<table>
<thead>
<tr>
<th v-for ="row in Result.Headers">
{{row}}
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in Result.Data ">
<td>
{{item.2020-Jan}} // Because don't know the number of properties until run time
</td> // No of <td/>'s can change on no of properties.
<td> // exactly don't know how many <td>'s needed there.
{{item.2020-Jul}}
</td> <td>
{{item.2021-Jan}}
</td>
</tr>
</tbody>
</table>
</div>
Is there way to bind these type of object to fronted in vue.js ?
You need to loop over the item's keys again. This will show all the values in the object
<tbody>
<tr v-for="item in Result.Data ">
<td v-for="(value, key, index) in item">
{{value}}
</td>
</tr>
</tbody>
If you want to filter some of them, for instance check that the keys are valid dates you need to add a v-if and use Date.parse to check for this.
<tbody>
<tr v-for="item in Result.Data ">
<td v-for="(value, key, index) in item" v-if="Date.parse(key) !== NaN">
{{value}}
</td>
</tr>
</tbody>
if u wana show all attr-> u can use this:
<ul v-for="item in Result ">
<li v-for="(value,key,index) in item">{{value}}</li>
</ul>
if u wana show all days u can use v-if and compute to complete youself fillter
<div id="app">
<ul v-for="item in Result" >
<li v-for="(value,key,index) in item" v-if="canShow(key)"> index:{{index}}------ key: {{key}} ------ value:{{value}} </li>
</ul>
</div>
<script>
var vue=new Vue({
el:'#app',
data:{
Result:[{
name: 'SkyManss',
2020-Jan: 1,
2020-Jul: 2
},{
name: 'SkyManss2',
2020-Jan: 1,
2020-Jul: 2,
2021-Jan: 3,
2021-Jul: 4
}]
},
computed:{
canShow(){
return function(skey){
return skey.indexOf('-') > -1;
}
}
}
});
</script>
after some research and some of your suggestions I came up with an answer.
<div>
<table>
<thead>
<tr>
<th v-for ="row in Result.Headers">
{{row}}
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in Result.Data ">
<td v-for="row in Result.Headers">
{{item[row]}}
</td>
</tr>
</tbody>
</table>
</div>
Javascript code
this.Result.Headers = Object.keys(result.data[0]);
this.Result.Data = result.data;
But this code only worked for the first time. second time data didn't get updated. So I updated JavaScript code to following code.
Vue.set(self.Result, 'Headers', []);
Vue.set(self.Result, 'Result', []);
this.Result.Headers = Object.keys(result.data[0]);
this.Result.Data = result.data;
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. That I got to know from following post.
vue.js is not updating the DOM after updating the array
Thank You All !!!

Vue-js v-for only posts first record on axios POST

I'm stuck and could use a nudge in the right direction!
Overview:
I pass a Laravel collection to my Vue.js component ( :collections_prop="{{ $collection }}" ). I'm using a <tr v-for to iterate over the collection in the component. Inside each <tr> is a form with a <select> element.
What I'm trying to accomplish:
When I change an individual <select>, the form should submit and include that particular items order_id to the backend.
What's going wrong:
My table is displayed as expected, and I can see the individual order_id's associated with each <tr>. The form submits, but only for the first item.
Example:
The first collection has an order_id of 10 and a qty of 5. If I select a new qty, the form submits with that order_id and the updated qty. Perfect!
However, if I change any other <select> tag, the order_id and qty of the first row is the only information submitted.
<template>
<div class="container-fluid">
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<table class="table table-sm">
<thead>
<th scope="col">Product</th>
<th scope="col">Quantity</th>
<th scope="col">$</th>
<th scope="col"><i class="fas fa-trash-alt"></i></th>
</thead>
<div v-if="collections.length">
<tr v-for="collection in collections" :key="collection.order_id">
<td>{{ collection.description }}</td>
<td>
<form #submit="updateQty">
<input type="hidden" name="type" :value="'qty'" id="type" />
<select class="form-control" id="qty" #change="updateQty" >
<option :value="collection.qty" >{{ collection.qty }}</option>
<option v-for="(x, index) in 200" :value="x-1" :key="index" >{{ index }}</option>
</select>
</form>
</td>
<td>{{ collection.value }}</td>
<td>{{ collection.order_id }}</td>
</tr>
</div>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['collections_prop'],
data() {
return {
collections: this.collections_prop,
qty: '',
type: '',
value: '',
order_id: '',
};
},
mounted() {
console.log('DisplayTable.vue mounted successfully');
var type = document.querySelector('#type').value;
},
methods: {
updateQty(e) {
e.preventDefault();
let url = '/update';
/** Uncomment to see variables being posted */
console.log('Order id: ' + order_id.value + ', QTY: ' + qty.value);
axios.post(url, {
qty: qty.value,
type: type.value,
order_id: order_id.value,
})
.then(response => {
this.collections = response.data;
console.log('Form submitted');
})
},
}
}
</script>
What I've tried:
Too many things to post, but highlights are that I've tried switching this
<tr v-for="collection in collections" :key="collections.order_id">
for this
<tr v-for="(collection, index) in collections" :key="index"> , but no luck.
I've tried to add a v-model to the <select> tag as well, and I think this is where I'm going wrong but I can't solve it.
I've tried:
<select id="qty" #change="updateQty" v-model="collection.order_id">, which causes the {{ collection.qty }} to not be displayed inside the <option> tag.
I've read about handling forms and form input bindings, which I'm SURE contains the answer, but I can't wrap my head around it.
Any assistance or even a link to an example would really help me out. Thank you very much in advance!
There are a few issues you'll want to take care of here. First of all, it doesn't seem like the form is really necessary, so I left it out of my example. If you do need it for whatever reason, it won't hurt to add back in.
Your change event on your select isn't passing back any data to the updateQty method, so it doesn't know how to get any of the data you're supposed to send to the API. If you change your select to look like this, the correct order for that row will get passed back to the method:
<select #change="() => change(event, item)" v-model="item.qty">
<option v-for="(x, index) in 200" :value="x" :key="index" >{{x}}</option>
</select>
Also, notice that I removed the first option in your select. That would have caused you to have duplicates - if qty was set to 4 for one of your orders, 4 would show up first in the dropdown and after e (would look like 4,1,2,3,4,etc). By setting v-model to collection.qty (not collection.order_id), the select will always contain the value of collection.qty for that row - if qty is 5, the select will automatically choose 5.
Now, we can set up for change handler to look like this:
updateQty(event, order) {
alert(`New quantity: ${order.qty}`)
var postObject = {
qty: order.qty,
type: 'Wherever this comes from',
order_id: order.order_id,
}
let url = '/update';
console.log(event)
/** Uncomment to see variables being posted */
console.log('Order id: ' + postObject.order_id + ', QTY: ' + postObject.qty);
// Do your post
axios.post(url, postObject)
}
The order parameter will always be the collection item from the row that was changed. Now, every time any of the selects change, your updateQty method can tell exactly which order it was for. collection.qty is also automatically updated, since v-model is 2-way bound.
Here's a JSFiddle showing all of this code working: http://jsfiddle.net/q4cLoz02/3/
Look at this part of your code:
<select class="form-control" id="qty" #change="updateQty" >
<option :value="collection.qty" >{{ collection.qty }}</option>
<option v-for="(x, index) in 200" :value="x-1" :key="index" >{{ index }}</option>
</select>
You iterate this HTML to all of the items in the collection, right? Ok, the user changes the selected value of the select element and it triggers the #chagne event. Now the question is HOW IT SHOULD UNDERSTAND WHAT COLLECTION QTY SHOULD CHANGE?.
Try this:
<select class="form-control" id="qty" #change="() => updateQty(event, collection.id)" >
<option :value="collection.qty" >{{ collection.qty }}</option>
<option v-for="(x, index) in 200" :value="x-1" :key="index" >{{ index }}</option>
</select>
And the updateQty method:
updateQty(e, order_id) {
var newQty = e.target.value;
alert(`${order_id} quantity has changed to ${newQty}`)
// call rest ...
//
}
I created a JSFiddle example here.

Vue template object key alternative reference without implicitly calling with key name

I have an object which is retrieved from an API and key names are unknown. Let's assume it will come in the following format:
var attributes = {
"Colour": ["red", "black", "purple"],
"Size": ["8.0", "8.5", "9.0", "9.5", "10.0"]}
How can I access this data directly in Vue without knowing keys? I know the following would work if I had known the keys, but I am looking for an option where I can refer to the arrays without knowing the key names, like normal Javascript values are accessed through key name (square brackets).
<table>
<tr>
<th v-for="(values, name) in attributes"> [[ name ]]</th>
</tr>
<td>
<select">
<option v-for="value in attributes.Colour"> [[ value ]] </option>
</select>
</td>
<td>
<select">
<option v-for="value in attributes.Size"> [[ value ]] </option>
</select>
</td>
</table>
I have tried this so far (e.g. attributes[name]), which does not seem to be correct Vue template syntax:
<table>
<tr>
<th v-for="(values, name) in attributes"> [[ name ]]</th>
</tr>
<td v-for="value in attributes[name]">
<select">
<option> [[ value ]] </option>
</select>
</td>
</table>
You need to do something like this.
Since your attributes is an object with dynamic keys, loop through the object to get the keys.
Then loop through each of the keys of the object attributes to get array list.
Also, the <td> tag should be wrapped inside a <tr> tag
function callMe(){
var vm = new Vue({
el : '#root',
data : {
attributes : {
"Colour": ["red", "black", "purple"],
"Size": ["8.0", "8.5", "9.0", "9.5", "10.0"]}
},
methods: {
}
})
}
callMe();
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.11/dist/vue.js"></script>
<div id='root'>
<table>
<tr>
<th v-for="(item, key, index) in attributes "> {{ key }} </th>
</tr>
<tr>
<td v-for="(item, key, index) in attributes">
<select>
<option v-for="name in item"> {{ name }} </option>
</select>
</td>
</tr>
</table>
</div>
</div>
You can use indexes. See: https://v2.vuejs.org/v2/guide/list.html#Mapping-an-Array-to-Elements-with-v-for.
<table>
<td>
<select v-for="(value, index) in attributes">
<option> {{ attributes[index] }} </option>
</select>
</td>
</table>
I'm also somehow lost in your code, because I'm not sure if you want to render more options, selectsor tds. In my example there will be rendered a number of options based on the attributes that you pass into select's v-for.

Vue template doesn't update

I'm building my first project in VueJS, and I'm having trouble getting a template to show/hide using v-if. I have a data model boolean variable (groups.categories.descEditable) that I am toggling to show/hide a template. For some reason the template isn't reactively updating itself when I change that value.
<tbody v-for="group in groups">
...
<tr v-for="cat in group.categories">
...
<td class="td-indent">
<input v-if="cat.descEditable" :value="cat.description" type="text" class="form-control">
<div v-else v-on:click="editDesc(cat.id)">{{ cat.description }}</div>
<div>{{cat.descEditable}}</div>
</td>
...
</tr>
</tbody>
methods: {
editDesc (cat_id) {
let vm = this
this.groups.forEach(function(group, gr_ind){
group.categories.forEach(function(cat, ind) {
if (cat_id == cat.id)
cat.descEditable = true
else
cat.descEditable = false
})
})
}
},
So I would like the text input to show if descEditable is true (once the div containing the description is clicked), otherwise show the div with the static description value. The descEditable property seems to be updating properly, but the v-if on the input element isn't reacting to it. I must be misunderstanding something fundamental to vuejs, just can't figure out what it is.
I think you can ditch the editDesc method entirely.
console.clear()
const groups = [
{
categories:[
{
descEditable: false,
description: "click me"
}
]
}
]
new Vue({
el:"#app",
data:{
groups
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<table>
<tbody v-for="group in groups">
<tr v-for="cat in group.categories">
<td class="td-indent">
<div v-if="cat.descEditable">
<input v-model="cat.description" type="text" class="form-control">
<button #click="cat.descEditable = false">Save</button>
</div>
<div v-else #click="cat.descEditable = true">{{ cat.description }}</div>
</td>
</tr>
</tbody>
</table>
</div>

Categories