Multiple select inputs in table header with unique models
<div id='app'>
<table class="table table-bordered" v-for="(file, index) in fileData" :key="index">
<thead>
<tr>
<th scope="col" v-for="(col, index2) in file[index]" :key="index2+index">
<b-form-select v-model="selectedValue[index+index2]" :options="options">
<template v-slot:first>
<b-form-select-option :value="null" disabled>Ignore</b-form-select-option>
</template>
</b-form-select>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index2) in file" :key="index2">
<td v-for="(data, index3) in row" :key="index3">
{{ data }}
</td>
</tr>
</tbody>
data: {
selectedValue: [],
mappedColumns: [],
selected: null,
options: [{
value: '1',
text: 'Option 1'
},
{
value: '2',
text: 'Option 2'
},
{
value: '3',
text: 'Option 3'
}
],
You are using a single value in v-model
for all the dropdowns. so, once you change a single dropdown. All of them gets changed.
Try the above solution, where I declared a new array in data
which is selectedValue
You can keep the data of which dropdown is selected in this array
Use v-on:change
and a function instead of v-model
Here is the solution for individual selection
new Vue({
el: "#app",
data: {
mappedColumns: [],
selected: null,
options: [{
value: '1',
text: 'Option 1'
},
{
value: '2',
text: 'Option 2'
},
{
value: '3',
text: 'Option 3'
}
],
fileData: [
[
["123", "21/11/2013", "Data", "Data"],
["234", "22/11/2013", "Data", "Data"],
["345", "12/09/2018", "Data", "Data"],
],
[
["123", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data",
"Data"
],
["234", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data",
"Data"
],
["345", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data",
"Data"
]
]
]
},
methods: {
getSelectedItem(a, b, c) {
console.log(a, b, c);
}
}
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/tether.min.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/bootstrap-vue.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/js/tether.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/bootstrap-vue.js"></script>
<title>Document</title>
<style>
#app {
padding: 20px;
height: 500px;
}
</style>
</head>
<body>
<div id='app'>
<table class="table table-bordered" v-for="(file, index) in fileData" :key="index">
<thead>
<tr>
<th scope="col" v-for="(col, index2) in file[index]" :key="index2">
<b-form-select v-on:change="getSelectedItem($event,index,index2)" :options="options">
<template v-slot:first>
<b-form-select-option :value="null" disabled>Ignore</b-form-select-option>
</template>
</b-form-select>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index2) in file" :key="index2">
<td v-for="(data, index3) in row" :key="index3">
{{ data }}
</td>
</tr>
</tbody>
</table>
</div>
<script src="table.js"></script>
</body>
</html>
I guess you can make this a component
The header:
Possible usage:
<thead
is="THeadSelect"
:options="header_row"
:length="length /*defaults to options.length*/"
:headers.sync="headers"
></thead>
The ComponentOptions
//*.js
const THeadSelect = {
template: "#theadselect",
mounted(){
// make sure headers is populated
this.$emit("update:headers",
this.headers
.concat(Array.from({length:this.length_}, _=>""))
.slice()
)
},
props: {
options: {
type: Array,
required: true,
},
length: Number,
headers: {
type: Array,
required: true
}
},
computed:{
length_:{
get(){
return this.length || this.options.length
},
set(l){
this.$emit("update:length",l)
}
},
filteredOptions(){
return this.options.filter(
option => !this.headers.includes(option)
)
}
}
}
The template
// *.html
<template id="theadselect">
<thead>
<tr>
<th
v-for="(i,index) in length_"
:key="index"
>
<select
v-model="headers[index]">
<option disabled value="">
Please select one
</option>
<option
v-if="headers[index]"
selected
>
{{headers[index]}}
</option>
<option
v-for="option in filteredOptions"
:key="option"
>
{{option}}
</option>
</select>
</th>
</tr>
</thead>
</template>
Example
const THeadSelect = {
template: "#theadselect",
mounted(){
// make sure headers is populated
this.$emit("update:headers",
this.headers
.concat(Array.from({length:this.length_}, _=>""))
.slice()
)
},
props: {
options: {
type: Array,
required: true,
},
length: Number,
headers: {
type: Array,
required: true
}
},
computed:{
length_:{
get(){
return this.length || this.options.length
},
set(l){
this.$emit("update:length",l)
}
},
filteredOptions(){
return this.options.filter(
option => !this.headers.includes(option)
)
}
}
}
new Vue({
components: {THeadSelect},
data(){
return {
headers: [],
length: 10
}
},
template: "#root"
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="theadselect">
<thead>
<tr>
<th
v-for="(i,index) in length_"
:key="index"
>
<select
v-model="headers[index]">
<option value="">
Please select one
</option>
<option
v-if="headers[index]"
selected
>
{{headers[index]}}
</option>
<option
v-for="option in filteredOptions"
:key="option"
>
{{option}}
</option>
</select>
</th>
</tr>
</thead>
</template>
<template id="root">
<div>
<table>
<caption>Sample usage with select</caption>
<thead
is="THeadSelect"
:options="['option1', 'option2', 'option3']"
:headers.sync="headers"
></thead>
<tbody>
<tr>
<td
v-for="(prop, index) in headers"
:key="prop+index"
>
{{ prop || '?'}}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<table id="app"></table>
For the body one can think about the value of the headers array. Maybe put array indeces or object properties instead of currently option values
So for multiple tables one can think about:
<template id="table">
<table
v-for="(table, index) in tables"
:key="'table-'+index"
is="TableSelect"
:headers="table[0]"
:rows="table.slice(1)"
>
</table>
</template>
And for TableSelect:
const TableSelect = {
props: ["headers", "rows"],
template: "#table-select",
data(){
return {
selectedHeaders: []
}
},
computed(){
mappedRows(){
return this.rows
.map(row=> row.map(
(cell, index) => ({[headers[index]]: cell})
).reduce((obj, val) => Object.assign(obj, val))
)}
}
}
<template id="table-select">
<table>
<thead
is="THeadSelect"
:options="headers"
:headers.sync="selectedHeaders"
></thead>
<tbody>
<tr
v-for="(row, index) in mappedRows"
:key="'row-'+index"
>
<td
v-for="cell in selectedHeaders"
:key="cell+index"
>
{{cell && row[cell || ""]}}
</td>
</tr>
</tbody>
</table>
</template>
there are errors in above code but due to lazyness and missing linter on so i will let it be - as it provides the basic idea.
And a running example on codesandbox:
https://lbm8l.csb.app/