Reusing a form with Vue.js - vue.js

I have a page with a list of things. I want the user to be able to click on an item in the list and open a modal dialog with a relatively complicated form with data for each item. I've implemented the form using Vue.js, and so far, it works, but I can't figure out how to get the Vue to switch from the data for one item to the data for another item, or do something equivalent to that.
The examples make it look like components are for small things and not entire forms. Here's the JavaScript from a JS fiddle with a hastily-coded example that's kind of similar to what I want to do.
HTML:
<table>
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">Data</th>
</tr>
</thead>
<tbody>
<tr data-identifier="1">
<td><a>Item 1</a></td>
<td class="json">{ "id": "1", "formData": { "whichOption": "1", "optionOneSetting": "ONE AYE", "optionTwoSetting": null, "optionThreeSetting": null } }</td>
</tr>
<tr data-identifier="2">
<td><a>Item 2</a></td>
<td class="json">{ "id": "2", "formData": { "whichOption": "2", "optionOneSetting": null, optionTwoSetting": "two-b", "optionThreeSetting": null } }</td>
</tr>
<tr data-identifier="3">
<td><a>Item 3</a></td>
<td class="json">{ "id": "3", "formData": { "whichOption": "2", "optionOneSetting": null, "optionTwoSetting": "two-c", "optionThreeSetting": null } }</td>
</tr>
<tr data-identifier="4">
<td><a>Item 4</a></td>
<td class="json">{ "id": "4", "formData": { "whichOption": "3", "optionOneSetting": null, "optionTwoSetting": null, "optionThreeSetting": "true" } }</td>
</tr>
<tr data-identifier="5">
<td><a>Item 5</a></td>
<td class="json">{ "id": "5", "formData": { "whichOption": "1", "optionOneSetting": "ONE AYE AGAIN", "optionThreeSetting": null } }</td>
</tr>
</tbody>
</table>
<div id="app" class="hidden">
<select v-model="formData.whichOption">
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<label v-if="formData.whichOption==1">1-A: <input type="text" v-model="formData.optionOneSetting" /></label>
<div v-if="formData.whichOption==2">
<label><input type="radio" name="optionTwoSetting" value="two-a" v-model="formData.optionTwoSetting" /> 2-A</label>
<label><input type="radio" name="optionTwoSetting" value="two-b" v-model="formData.optionTwoSetting" /> 2-B</label>
<label><input type="radio" name="optionTwoSetting" value="two-c" v-model="formData.optionTwoSetting" /> 2-C</label>
</div>
<div v-if="formData.whichOption==3">
<label><input type="checkbox" name="optionThreeSetting" value="three" v-model="formData.optionThreeSetting" /> 3-A</label>
</div>
<button v-on:click="doMagic">Do a Thing</button>
</div>
JavaScript:
$(document).ready(function() {
$("a").click(function() {
var data = $(this).closest("tr").find("td.json").text();
data = JSON.parse(data);
if (!document.vue) {
document.vue = new Vue({
el: "#app"
, data: data
, methods: {
doMagic: function(event) {
var $tr = $("tr[data-identifier=" + this.id + "]");
console.log($.extend({}, this));
$tr.find(".json").text(JSON.stringify({
"id": this.id
, "formData": this.formData
}));
}
}
})
$("#app").removeClass("hidden");
}
else {
// This isn't expected to work, but it's effectively what I'd like to happen.
document.vue.data = data;
}
})
})
https://jsfiddle.net/don01001100/eywraw8t/370396/

Related

display a foreign data in a table vuejs

I am creating a page, in which a section called product, I show data and in this I have a data called id_categoria, where instead of showing the id I want to show the name of that category in question, this data is in the table of categories and in the product table I have the id. I already managed to show the data, but not this, besides that I managed to save and edit it in question, it is only necessary in show.
When working with vuejs I saw that you have to use a v-if but I do not know how to do it, or at least the attempts I have made have been wrong.
this is the code of the table where I show the data
<div class="card-body table-responsive">
<table id="example1" class="table table-bordered table-striped">
<thead>
<tr>
<th>id_producto</th>
<th>imagen</th>
<th>código</th>
<th>producto</th>
<th>categoria</th>
<th>stock</th>
<th>precio_compra</th>
<th>precio_venta</th>
<th>fecha</th>
<th colspan="2" class="text-center">Acciones</th>
</tr>
</thead>
<tbody>
<tr v-for="pro in productos" :key="pro.id_productos">
<th>{{pro.id_productos}}</th>
<td>
<img :src="pro.imagen" class="img-circle elevation-2" alt="Product Image" width="60" />
</td>
<td>{{pro.codigo}}</td>
<td>{{pro.producto}}</td>
<td>{{pro.id_categoria}}</td>
<td>{{pro.stock}}</td>
<td>{{pro.precio_compra}}</td>
<td>{{pro.precio_venta}}</td>
<td>{{pro.fecha}}</td>
<td class="text-center">
<button #click="modificar=true; abrirModal(pro)" type="button" class="editar btn btn-primary"><i class="fa fa-pencil-alt"></i></button>
</td>
<td class="text-center">
<button #click="eliminar(pro.id_productos,pro.producto)" type="button" class="eliminar btn btn-danger" data-toggle="modal" data-target="#modalEliminar"><i class="fas fa-dumpster-fire"></i></button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>id_producto</th>
<th>imagen</th>
<th>código</th>
<th>producto</th>
<th>categoria</th>
<th>stock</th>
<th>precio_compra</th>
<th>precio_venta</th>
<th>fecha</th>
<th colspan="2" class="text-center">Acciones</th>
</tr>
</tfoot>
</table>
</div>
and this is the script to bring the info to me
<script>
import axios from "axios";
export default {
watch: {
$route: {
immediate: true,
handler(to, from) {
document.title = to.meta.title || 'Productos';
}
},
},
data() {
return{
productosdatos:{
id_producto: "",
codigo: "",
producto: "",
stock: "",
precio_compra: "",
precio_venta : "",
id_categoria: "",
},
id: 0,
modificar: true,
modal: 0,
tituloModal: '',
productos:[],
categorias:[],
}
},
methods: {
async listarcategorias(){
const res = await axios.get('http://localhost:8000/categoria/');
this.categorias = res.data;
console.log(this.categorias)
},
async listar(){
const res = await axios.get('http://localhost:8000/productos/');
this.productos = res.data;
console.log(this.productos)
},
cerrarModal(){
this.modal = 0;
}
},
created() {
this.listar();
}
}
</script>
as you can see I have a variable called products, where is the id_category corresponding to the product, and categories where I bring all the category info.
the table looks something like this:
how can I make it not show the id of the category but the name of the category in question ?
pd: the category data is received in json as follows:
{
"id_categoria": 8,
"categoria": "Electrodomesticos",
"fecha": "2021-10-24 13:55:00"
}
thank you if you can help me to show the name of the category thank you
You can implement a function like this:
const findCategory = (id)=>{this.categorias.find(category=>category.id_categoria===id)?.categoria}
And in the template:
<td>{{findCategory(pro.id_categoria)}}</td>
Only one small change. In your code you have to edit. pro.id_categoria to pro.categoria. see comment inline.
<tr v-for="pro in productos" :key="pro.id_productos">
<th>{{pro.id_productos}}</th>
<td>
<img :src="pro.imagen" class="img-circle elevation-2" alt="Product Image" width="60" />
</td>
<td>{{pro.codigo}}</td>
<td>{{pro.producto}}</td>
// this line
<td>{{pro.id_categoria}}</td>
// edit to
<td>{{ pro.categoria }} </td>

Change in model for a p-dropdown placed in table cell in all columns changes the value for p-dropdwon in another row for the same column

Attached is the image to show how is the data been shown in table.
I have a nested json object like below. the main object i am showing as a row in prime ng table and on click of row expand of the row i am showing the data under description attribute in again a primeng table.
{
"Installed":[
{
"id":"1#2",
"Operational_State":"Installed",
"A Sne-Shelf":"20917118-CN32SLOT",
"A Slot-Card":"SLOT-1-MOTR",
"Z Sne-Shelf":"20917120-CN32SLOT",
"Z Slot-Card":"SLOT-1-MOTR",
"Optical End":"20917118:1-3-1:20917120:1-2-1",
"Channel Capacity":"194.60-100GB",
"A Slot Available":"2",
"Z Slot Available":"1",
"Capacity Available":"40",
"Description":[
{
"id":"1#2#1",
"A_end":[
{
"A_Equipment":20917117,
"A_Position":[
"1-6-2-1"
],
"A_Status":"Potential",
"A_FORECASTED_SFP":[
"XFP 1310",
"SFP",
"gp"
]
}
],
"Z_end":[
{
"Z_Equipment":20917119,
"Z_Position":[
"1-3-2-1"
],
"Z_Status":"Installed",
"Z_FORECASTED_SFP":[
"SFP 1310"
]
}
]
},
{
"id":"1#2#2",
"A_end":[
{
"A_Equipment":20917117,
"A_Position":[
"1-6-1-1"
],
"A_Status":"Potential",
"A_FORECASTED_SFP":[
"XFP 1310",
"SFP",
"gp"
]
}
],
"Z_end":[
{
"Z_Equipment":20917119,
"Z_Position":[
"1-3-1-1"
],
"Z_Status":"Installed",
"Z_FORECASTED_SFP":[
"SFP 1310"
]
}
]
},
{
"id":"1#2#3",
"A_end":[
{
"A_Equipment":20917117,
"A_Position":[
"1-6-3-1"
],
"A_Status":"Installed",
"A_FORECASTED_SFP":[
"XFP 1310"
]
}
],
"Z_end":[
{
"Z_Equipment":20917119,
"Z_Position":[
"1-3-3-1"
],
"Z_Status":"Potential",
"Z_FORECASTED_SFP":[
"XFP 1310",
"SFP",
"MOTR"
]
}
]
},
{
"id":"1#1#1",
"A_end":[
{
"A_Equipment":20917117,
"A_Position":[
"1-6-2-1"
],
"A_Status":"Potential",
"A_FORECASTED_SFP":[
"XFP 1310",
"SFP",
"gp"
]
},
{
"A_Equipment":20917117,
"A_Position":[
"1-6-1-1"
],
"A_Status":"Potential",
"A_FORECASTED_SFP":[
"XFP 1310",
"SFP",
"gp"
]
},
{
"A_Equipment":20917117,
"A_Position":[
"1-6-3-1"
],
"A_Status":"Installed",
"A_FORECASTED_SFP":[
"XFP 1310"
]
}
],
"Z_end":[
{
"Z_Equipment":20917119,
"Z_Position":[
"1-3-2-1"
],
"Z_Status":"Installed",
"Z_FORECASTED_SFP":[
"SFP 1310"
]
}
]
}
]
}
]
}
html for the same
<p-table #dt [columns]="selectedColumns2" sortField="Operational_State" [resizableColumns]="true" (onFilter)="onFilter($event, dt)" columnResizeMode="expand" responsive="true" [defaultSortOrder]="-1"
[ngStyle]="{'width': 'auto'}" columnResizeMode="fit" selectionMode="single" [autoLayout]="true" [scrollable]="false" [value]="details" dataKey="id"
[paginator]="paginatorPlanned" [rows]="30" [(selection)]="selectedRecord" (onRowSelect)="onRowSelect($event)">
<ng-template pTemplate="caption" let-columns>
<div class="ui-helper-clearfix row" style="width:100%;">
<div class="col-md-4" [ngStyle]="{'text-align':'left'}">
<!-- <p-multiSelect [options]="cols" [(ngModel)]="selectedColumns2" optionLabel="header" maxSelectedLabels=0 selectedItemsLabel="{0} columns selected"
[ngStyle]="{minWidth: '200px'}" defaultLabel="Choose Columns">
</p-multiSelect> -->
</div>
<div class="col-md-2"></div>
<div class="col-md-6 ml-0" [ngStyle]="{'text-align':'right'}">
<button type="button" (click)="onResetAll($event, dt,dt1)" class="btn bt-button" [ngStyle]="{'margin-right': '0.5em','height': '35px'}">
<Span>Clear Filter</Span>
</button>
<button type="button" (click)="exportToExcel()" [ngStyle]="{'margin-right':'0.5em'}" class="btn bt-button ml-4">
<i class="fa fa-file-excel-o mr-2" aria-hidden="true"></i>
<span>Export</span>
</button>
</div>
</div>
<!-- <div>
<table>
<tr>
<th></th>
<th *ngFor="let col of selectedColumns2" [ngSwitch]="col.field" [ngStyle]="{'overflow': 'visible'}">
<p-multiSelect #cmp *ngSwitchCase=col.field [options]=col.options defaultLabel="Select Items" (onChange)="dt.filter($event.value, col.field, 'in');updateFilter(dt,$event.value,col.field);"
selectedItemsLabel="{0} item selected" maxSelectedLabels=0 [(ngModel)]="selectedItem[col.field]" defaultLabel="Select Items"
(onChange)="dt.filter($event.value, col.field, 'in')" selectedItemsLabel="{0} item selected" maxSelectedLabels=0
[styleClass]="selectedItem[col.field]!=null ? 'ui-default-label' : ''"></p-multiSelect>
</th>
</tr>
</table>
</div> -->
</ng-template>
<ng-template pTemplate="header" let-columns>
<tr [ngStyle]="{'white-space':'nowrap'}">
<th style="width: 3rem"></th>
<th [ngStyle]="{'width':'auto'}" *ngFor="let col of columns" pReorderableColumn [pSortableColumn]="col.field" pResizableColumn>
{{col.header}}
<p-sortIcon [field]="col.field" ariaLabel="Activate to sort" ariaLabelDesc="Activate to sort in descending order" ariaLabelAsc="Activate to sort in ascending order"></p-sortIcon>
</th>
</tr>
<tr>
<th></th>
<th *ngFor="let col of columns" [ngSwitch]="col.field" [ngStyle]="{'overflow': 'visible'}">
<p-multiSelect #cmp *ngSwitchCase=col.field [options]=col.options defaultLabel="Select Items" (onChange)="dt.filter($event.value, col.field, 'in');updateFilter(dt,$event.value,col.field);"
selectedItemsLabel="{0} item selected" maxSelectedLabels=0 [(ngModel)]="selectedItem[col.field]" defaultLabel="Select Items"
(onChange)="dt.filter($event.value, col.field, 'in')" selectedItemsLabel="{0} item selected" maxSelectedLabels=0
[styleClass]="selectedItem[col.field]!=null ? 'ui-default-label' : ''"></p-multiSelect>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-rowData let-columns="columns" let-expanded="expanded" let-rowIndex="rowIndex">
<tr *ngIf="rowGroupMetadata[rowData.Operational_State].index === rowIndex">
<td></td>
<td colspan="10">
<span class="p-text-bold p-ml-2" [class]="'route-badge-status-' + rowData.Operational_State">
{{rowData.Operational_State | uppercase}}
</span>
</td>
<!-- <button type="button" pButton pRipple [pRowToggler]="rowData" class="p-button-text p-button-rounded p-button-plain p-mr-2" [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
<span class="p-text-bold p-ml-2">{{rowData.operational_state | uppercase}}</span> -->
</tr>
<tr [pSelectableRow]="rowData" [ngStyle]="{'white-space':'nowrap'}">
<td>
<button type="button" pButton pRipple [pRowToggler]="rowData" class="p-button-text p-button-rounded p-button-plain" [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'" title="Expand"></button>
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-warning ml-2" title="Draw" (click)="drawRoute(rowData)"></button>
</td>
<td *ngFor="let col of columns" class="ui-resizable-column">
{{rowData[col.field]}}
</td>
</tr>
</ng-template>
<ng-template pTemplate="rowexpansion" let-rowData>
<tr [ngStyle]="{'white-space':'nowrap'}">
<td colspan="10">
<div class="" style="display: flex;justify-content: space-around;">
<div>
<p-table #dt2 [value]="rowData.Description" dataKey="id" styleClass="p-datatable-striped" [ngStyle]="{'width': 'auto'}" [paginator]="false" [rows]="10" [columns]="selectedDetailColumns">
<ng-template pTemplate="header">
<!-- <tr>
<th class="childHeader" colspan="3">ASNE</th>
<th class="childHeader" colspan="3">ZSNE</th>
</tr> -->
<tr>
<th class="childHeader" [ngStyle]="{'width':'auto'}" class="customheader1" *ngFor="let col of detailColumns" pReorderableColumn [pSortableColumn]="col.field" pResizableColumn>
{{col.header}}
<p-sortIcon [field]="col.field" ariaLabel="Activate to sort" ariaLabelDesc="Activate to sort in descending order" ariaLabelAsc="Activate to sort in ascending order"></p-sortIcon>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-detail let-rowIndex="rowIndex">
<tr>
<td *ngFor="let col of detailColumns; let i=index" class="ui-resizable-column">
<div *ngIf="col.showDropdown" style="margin-left:-8px;">
<!-- <p-dropdown #cmp1 [options]="initial?(detail|field:col:rowIndex): detail[col.subfield]" [(ngModel)]="detail[col.subfield]"
id="{{detail.id}}_{{col.subfield}}" scrollHeight="400px" appendTo="body" (onChange)="updateFields(dt2,$event,col,detail,rowIndex,rowData)"
dataKey="value" [styleClass]="detail[col.field]!=null ? 'ui-default-label' : ''">
</p-dropdown> -->
<p-dropdown #cmp1 [options]="initial?(detail|field:col:rowIndex): selectedField[col.subfield]" [(ngModel)]="selectedRowCol[rowIndex]" optionValue="label"
id="{{rowIndex}}_{{i}}_{{col.subfield}}" scrollHeight="400px" appendTo="body" (onChange)="updateFields(dt2,$event,col,detail,rowIndex,rowData,i)"
dataKey="value">
</p-dropdown>
</div>
<div *ngIf="!col.showDropdown">
<!-- <ng-container *ngFor="let item of detail[col.field]"> -->
{{initial?(detail|field:col:rowIndex): selectedField[col.subfield]}}
<!-- </ng-container> -->
</div>
</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
</td>
</tr>
</ng-template>
</p-table>
I have created a forloop to display the dropdown in each row column cell. Initially data loads properly but when I change the value of one dropdown it changes the value for the same dropdown in another row.

Multiple select inputs in table header with unique models

I am receiving data from the backend that takes the following format
[
[
[ "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" ]
]
]
Each fileData represents a table, so in the above example it should produce two tables. The data inside contains a tables rows, so each table above has two rows.
In order to achieve this I am doing something like the following.
<table class="table" v-for="(file, index) in fileData" :key="index">
<tbody>
<tr v-for="(row, index2) in file":key="index2">
<td v-for="(data, index3) in row" :key="index3">
{{ data }}
</td>
</tr>
</tbody>
</table>
This all seems to work fine. However, the data I am using does not have headers, but I need to prodive a header for each column that contains a select. As such I have added the following
<table class="table" 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-model="options.value" :options="options"></b-form-select>
</th>
</tr>
</thead>
</table>
Once again this seems to work. My problem is that I want the user to define what a column represents, using the select. At the moment, if I select something, they all change.
I have produced this Fiddle as an example https://jsfiddle.net/mhyv62bt/1/
How can I make the selects independent, and is it also possible to remove an option once selected?
Thanks
This seems to produce the correct number of header columns for each table.
Update
I have a slightly different setup so trying to fit it in with my project. As such, I created the file THeadSelect.vue
<template id="theadselect">
<thead>
<tr>
<th
v-for="(i,index) in this.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>
<script>
export default {
mounted () {
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)
)
}
}
}
</script>
I am then trying to use this within my page
<template>
<div>
<b-form
novalidate
#submit.stop.prevent=""
>
<div class="row">
<div class="col-12">
<table class="table table-bordered" v-for="(file, index) in fileData" :key="index">
<thead
is="THeadSelect"
:options="['option1', 'option2', 'option3']"
:headers.sync="headers"
></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>
</div>
</b-form>
</div>
</template>
<script>
import { THeadSelect } from '#/components/common/THeadSelect'
export default {
components: {
THeadSelect
},
computed: {
fileData () {
return this.$store.getters.fileData
}
},
data () {
return {
headers: [],
length: 10,
}
}
}
</script>
It is a bit messed up though. Only 3 selects are being displayed for each table. Additionally, if I select an option in table 1, it selects the same option in table 2. If you check out my original fiddle you can see the initial data I am trying to work with, so there will always be two tables.
<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
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/
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/tether#1.4.7/dist/css/tether.min.css">
<link rel="stylesheet" href="https://unpkg.com/bootstrap-vue#2.15.0/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/vue#2.6.11/dist/vue.js"></script>
<script src="https://unpkg.com/tether#1.4.7/dist/js/tether.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.15.0/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>

Vuejs2- Avoid repetition of select field options using vee-validate

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/

Bootstrap Select and dynamic rows issue in vue.js

I have the following dynamic table/rows in vue.js and I use bootstrap-select to have a nicer dropdown select. The form has a add/remove line to be dynamic. I cannot load the options of a select using bootstrap select. The select appears on each row but no dropdown list appears.
What am I doing wrong?
here goes my jsfiddle
HTML:
<div id="app">
<table class="table">
<thead>
<tr>
<td><strong>Date</strong></td>
<td><strong>Account</strong></td>
<td><strong>Debit</strong></td>
<td><strong>Credit</strong></td>
<td></td>
</tr>
</thead>
<tbody>
<tr v-for="row in rows">
<td>
<input type="date" v-date="row.myDate">
</td>
<td>
<select class="selectpicker" ref="select" v-model="row.select">
<option value="Acc1">Account1</option>
<option value="Acc2">Account2</option>
<option value="Acc3">Account3</option>
<option value="Acc4" selected>Account4</option>
</select>
</td>
<td>
<input type="text" v-model="row.debit" v-on:keypress="isNumber(event)">
</td>
<td>
<input type="text" v-model="row.credit" v-on:keypress="isNumber(event)">
</td>
<td><a #click="removeRow(row)">Remove</a></td>
</tr>
</tbody>
<tfooter>
<td class="al-g"> <button class="button btn-primary" #click="addRow">Add Line</button></td>
<td></td>
<td class="al-r">tot D.: {{ totaldebit | roundme }}</td>
<td class="al-r">tot Cr.:{{ totalcredit | roundme}}</td>
<td class="al-r">Dif: {{ totaldebit-totalcredit | roundme}}</td>
</tfooter>
</table>
</div>
JS:
Vue.filter('roundme', function (value) {
return value.toFixed(3);
})
var app = new Vue({
el: "#app",
data: {
rows: [{debit:0, credit:0},
]
},
computed: {
totaldebit() {
return this.rows.reduce((total, row) => {
return total + Number(row.debit);
}, 0);
},
totalcredit() {
return this.rows.reduce((total, row) => {
return total + Number(row.credit);
}, 0);
}
},
methods: {
addRow: function() {
this.rows.push({myDate:"",
account:"",
debit: "",
credit: ""
});
},
removeRow: function(row) {
//console.log(row);
this.rows.$remove(row);
},
isNumber: function(evt) {
evt = (evt) ? evt : window.event;
var charCode = (evt.which) ? evt.which : evt.keyCode;
if ((charCode > 31 && (charCode < 48 || charCode > 57)) && charCode !== 46) {
evt.preventDefault();;
} else {
return true;
}
}
}
});
You are going to need a wrapper component like this.
The way bootstrap-select normally activates is to scan the HTML at startup and apply itself to .selectpicker elements. That won't work if the DOM is dynamic, as it is with Vue. You have to activate the elements using the $(element).selectpicker() method as they are created or updated.
See also Make VueJS and jQuery play nice.