How can I extend a table with backend data by columns instead of rows in Vue.js? - javascript

I have a table layout that currently looks like this:
This works well because I can place all my data for one row inside of my item object, and just render the TableRow object as <tr><td>...</td><td>...</td>....
One solution could be to subdivide my item object by column, and return only the corresponding value:
<tbody>
<tr v-for="(item, rowIndex) in store.selectedItems" :key="item.id">
<!-- iterate over each column within each row -->
<td v-for="(col, colIndex) in columns" :key="colIndex">
{{ item[col.field] }}
</td>
</tr>
</tbody>
However, ideally I would like to define the whole column in a single TableCol component instead of having to render the entire table like this. Is this somehow possible?

As per my understanding you want to create a separate shared component for column, So that you can reuse that anywhere by just passing a dynamic props. If Yes, Here you go :
Vue.component('tableCol', {
props: ['column', 'rowitem'],
template: `<td>{{ rowitem[column.field] }}</td>`
});
var app = new Vue({
el: '#app',
data: {
store: {
selectedItems: [{
id: 1,
name: 'Alpha',
age: 21
}, {
id: 2,
name: 'Beta',
age: 25
}, {
id: 3,
name: 'Gamma',
age: 30
}]
},
columns: [{
field: 'name'
}, {
field: 'age'
}]
}
});
table, tr, td {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<tbody>
<tr v-for="item in store.selectedItems" :key="item.id">
<template>
<table-col v-for="(column, index) in columns" :column="column" :rowitem="item" :key="index"/>
</template>
</tr>
</tbody>
</table>
</div>

Related

How to check table column data type

I am trying to make table configurable. Before that I am making demo component through which I can make a my own configurable table.
I want to pass few things to my table like, column names and data.
Column Name I want to pass like this:
headers = [
{name: 'Sr.No'},
{name: 'name', dataType: 'text'},
{name: 'mobile', dataType: 'number'},
{name: 'date', dataType: 'date'},
{name: 'Action'}
];
the dataType above states that this column will have input type text when is editable, same for number and date as well.
I want to show input boxes based on this values, but in <td> I am not able to check this dataType key. I tried this:
html
<table class="row-border border-1 table">
<thead>
<tr>
<th *ngFor="let head of headers">{{head.name}}</th>
</tr>
</thead>
<tr *ngFor="let tableData of data; let i=index">
<td>{{i+1}}</td>
<ng-container *ngIf="tableData.isEditable; else viewable">
<div *ngIf="tableData.dataType ==='text'">
<input type="text">
</div>
<div *ngIf="tableData.dataType ==='date'">
<input type="date" >
</div>
<div *ngIf="tableData.dataType ==='number'">
<input type="date">
</div>
</ng-container>
<td>
<button *ngIf="!tableData.isEditable" (click)="onEdit(tableData)">Edit</button>
<button *ngIf="!tableData.isEditable">Delete</button>
<button *ngIf="tableData.isEditable" (click)="onSave(tableData)">Update</button>
</td>
<ng-template #viewable>
<td>{{tableData.name}}</td>
<td>{{tableData.mobile}}</td>
<td>{{tableData.date}}</td>
</ng-template>
</tr>
</table>
ts file
headers = [
{name: 'Sr.No'},
{name: 'name', dataType: 'text'},
{name: 'mobile', dataType: 'number'},
{name: 'date', dataType: 'date'},
{name: 'Action'}
];
data = [
{id:1, name: 'sam', mobile: '8788888888', date: '20/11/2021', isEditable: false},
{id:2, name: 'joy', mobile: '9788888888', date: '22/11/2021', isEditable: false},
]
onEdit(data) {
this.data.map((item) => {
item.isEditable = data.id === item.id;
})
}
onSave(data) {
data.isEditable = false;
}
Is there any way, so that I can check the column dataType and based on that I am able to show that input box in that cell of row when I click on edit button? Thanks in advance!!!
Oh dear lord have I spent much time in my past to create beautiful and editable tables.
I know how annoying this can be so I took my time and looked at your example.
Here is a quick and very dirty version I took your example.
You need to be able to map the headers to the properties of the data otherwise you can't identify which column represents which values as it is currently hardcoded in your example.
headers = [
{name: 'Sr.No'},
{name: 'name', dataType: 'text', mappedProperty: 'name'},
{name: 'mobile', dataType: 'number', mappedProperty: 'mobile'},
{name: 'date', dataType: 'date', mappedProperty: 'date'},
{name: 'Action'}];
Furthermore you need to iterate through the headers in the view template to get the mappedProperty and to figure out which dataType it has.
Here is the possible solution:
<table class="row-border border-1 table">
<thead>
<tr>
<th *ngFor="let head of headers">{{head.name}}</th>
</tr>
</thead>
<tr *ngFor="let tableData of data; let i=index">
<td>{{i + 1}}</td>
<ng-container *ngIf="tableData.isEditable; else viewable">
<ng-container *ngFor="let head of headers">
<ng-container *ngIf="head.mappedProperty">
<td>
<input [type]="head.dataType" [(ngModel)]="tableData[head.mappedProperty]">
</td>
</ng-container>
</ng-container>
</ng-container>
<td>
<button *ngIf="!tableData.isEditable" (click)="onEdit(tableData)">Edit</button>
<button *ngIf="!tableData.isEditable">Delete</button>
<button *ngIf="tableData.isEditable" (click)="onSave(tableData)">Update</button>
</td>
<ng-template #viewable>
<td>{{tableData.name}}</td>
<td>{{tableData.mobile}}</td>
<td>{{tableData.date}}</td>
</ng-template>
</tr>
</table>
And here is the onEdit function. In my opinion you just need to toggle isEditable. But I am unsure what you were trying in the example so sorry for any misunderstand:
onEdit(tableData: any) {
tableData.isEditable = !tableData.isEditable;
}
Have fun with the example and if you need any help let me know.
Here is the running example:
Important: This is way to much to write for each table.
Do yourself a favore and abstract it into a component and reuse it. Some interface like this should work. And the code is quite similar to the current.
<my-table-component [columnDefinition]="headers" [rows]="data"></my-table-component>

In my code i am tring to loop thorugh an object. Would this Vue js code be considered "AntiPattern"?

I'm new to Vue js and want to see if this proper coding practice. I am using v-for to loop through the objects but am not sure if this an antipattern. The first object is what i'd like to use to build the header colum. The next object(s) would be the item information. To clarify i am not sure if looping through an object this way is bad practice. I am looping through the first item to use the values as an index so that i can create rows for property in each object.
<thead>
<tr >
<th v-for="comparison in cartComparisons" class="flex-1 bg-gray-200 border-b border-white">
<a v-if="comparison.model.link" :href="comparison.model.link | ''">
{{comparison.model.name}}
</a>
<template v-else>{{comparison.model.name}}</template >
</th>
</tr>
</thead>
<tbody >
<template v-for='(comparison,key,index) in cartComparisons[0]'>
<tr v-if="index !== 0">
<td v-for="comparison in cartComparisons">
{{comparison[key]}}
</td>
</tr>
</template>
</tbody>
<script>
export default{
props:{
product: Object,
},
data () {
return {
cartComparisons: [
{
model: {
name: "Model",
},
price: 'Our Price',
frame: 'Frame',
frameTreatment: 'Frame Treatment',
loadCapacity: 'Load Capacity',
folding: 'Folding',
wheels: 'Wheels',
straps: 'Straps',
kickstand: 'Kickstand',
padding: 'Padding',
hardware: 'Hardware',
storageBag: 'Storage Bag',
},
{
model: {
name: "bike",
},
price: "$45.95",
frame: "Aluminum",
frameTreatment: "Treated",
loadCapacity: "65lbs",
folding: "Yes",
wheels: '7"',
straps: "3 Included",
kickstand: "Single Leg",
padding: "Rubber",
hardware: "Stainless Steel",
storageBag: "Bag included",
},
],
};
},
};
</script>

Checkboxes for Hide/Show columns in Vue.js table not working properly

I made a vue.js bootstrap table for loading some data from local JSON files.
I'm trying to implement show/hide columns via checkboxes.
I think I've solved most of the problem, but the problem is when I hide a column and then press on that same checkbox again (to make column visible again) I lose the order of table (that column becomes last column) and so on.
For example if I hide "Timestamp" column which is first table header in my table and then press to show it again it is no longer on first place, instead it gets created on last place.
https://imgur.com/BaTfgci --> this is how app looks right now
https://codepen.io/frane_caleta/pen/KKPMKrL --> codepen of my code, you won't be able to load it without JSON file though
https://imgur.com/a/23jx0lZ --> JSON data example
First time asking question here, so feel free to ask me if you need some more information to solve the problem :)
<b-form-group label="Hide columns: ">
<b-form-checkbox-group id="checkbox-group-1" v-model="selected" :options="fields" name="flavour-1">
</b-form-checkbox-group>
</b-form-group>
//my table
<b-table id="myTable"
:small="small"
:bordered="bordered"
hover head-variant="dark"
stacked="md"
:items="cptItems"
:fields="selected"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
#filtered="onFiltered"
:tbody-tr-class="rowClass"
v-if="selected.length > 0">
</b-table>
//Javascript file
function initializeVue() {
return new Vue({
el: '#app',
data() {
return {
items: data.logdatas,
selected: [],
fields: [{
text: 'Origin',
value: {
key: 'origin',
label: 'Origin',
sortable: true,
class: 'text-center',
index: 0
}
},
{
text: 'Timestamp',
value: {
key: 'timeStamp',
label: 'Timestamp',
sortable: true,
class: 'text-center',
index: 1
}
},
{
text: 'Level',
value: {
key: 'level',
label: 'Level',
sortable: true,
class: 'text-center',
index: 2
}
}, ...there are 4 more fields here like this...
//my method for creating those checkboxes
created() {
this.selected = this.fields.map(field => field.value);
}
the selected data is your culprit. b-checkbox-group :selection lists items in order of selection.
example2
b-table :fields lists columns in the order of the items.
better make a static fields-list and filter by selection.
// make this data or property
let columnNames = ["one", "two", "three", "infinity", "pi"];
// make this data
let selected = []
//make this computed // can be optimized
let activeFields = columNames.filter(name => selected.includes(name))
// more like this
export default {
data(){
return {
selected: [],
columnNames: ['name1', 'name2']
},
computed(){
activeColumns(){
return this.columnNames.filter(this.selected.includes) || []
}
}
const app = new Vue({
data(){
return {
currentPage: 0,
perPage: 10,
fields: ['age', 'first_name', 'last_name'],
//selected: [],
selected: ['age', 'first_name', 'last_name'],
items: [
{ age: 40, first_name: 'Dickerson', last_name: 'Macdonald' },
{ age: 21, first_name: 'Larsen', last_name: 'Shaw' },
{ age: 89, first_name: 'Geneva', last_name: 'Wilson' },
{ age: 38, first_name: 'Jami', last_name: 'Carney' }
]
}
},
computed: {
activeFields(){
return this.fields.filter(name => this.selected.includes(name))
}
}
}).$mount("#app");
<!-- Add this to <head> -->
<!-- Load required Bootstrap and BootstrapVue CSS -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<!-- Load polyfills to support older browsers -->
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<!-- Load Vue followed by BootstrapVue -->
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<div id="app">
<b-form-group label="Hide columns: ">
<b-form-checkbox-group id="checkbox-group-1" v-model="selected" :options="fields" name="flavour-1">
</b-form-checkbox-group>
</b-form-group>
<b-table id="myTable"
:bordered="true"
hover head-variant="dark"
stacked="md"
:items="items"
:fields="selected"
:current-page="currentPage"
:per-page="perPage"
tbody-tr-class="row-class"
v-if="selected.length > 0">
</b-table>
<b-table id="myTable"
:bordered="true"
hover head-variant="dark"
stacked="md"
:items="items"
:fields="activeFields"
:current-page="currentPage"
:per-page="perPage"
tbody-tr-class="row-class"
v-if="selected.length > 0">
</b-table>
</div>

table in Angular using dynamic table headers

I was creating an angular app to display data from various data sources. I configured the list of various data sources in a JSON file each data source has a set of values attached to it.
Here is an example
var configurableConfigurations=[
{
name:"Locations",
table:"location_set",
columnList:[
{
name:"LOCATION_NAME",
alias:"Location",
primary:true
},
{
name:"DESCRIPTION",
alias:"Description",
primary:false
}
]
},
{
name:"System Parameters",
table:"system_params",
columnList:[
{
name:"param_Key",
alias:"Parameter",
primary:true
},
{
name:"param_Value",
alias:"Value",
primary:false
}
]
}
];
I then created an HTML page to display this using angular : the page has 2 parts
1. A select box which shows various parameters this is done using ng-repeat
<select name="ConfigurationName" ng-model="selected" ng-options="eachConfiguration.name for eachConfiguration in configurableConfigurations" ng-change="fetchRequiredConfiguration()">
A table which I want to generate using the headers of the parameter selected
this is my code to do that
<table id="configtable">
<thead>
<tr>
<th class="col-md-1" ng-repeat="column in selected.columnList" >{{column.alias}}</th>
</tr>
</thead>
This works great for the first time. But when the option is selected again using the select box the table header is not shown.
The table data is being populated properly , its just the table headers that are getting messed up.
Could anyone please help me get around this problem. I am new to angularjs. May be I am missing something important.
Edit ::
I should Also Mention that I fetch the data from the API and then was using the Data table plugin(https://www.datatables.net/download/) to show this as Data
$("#configtable").DataTable({
"ajax": "../fetchvalue/config/"+this.selected.table,
destroy: true,
"columns":{ "data": "ColumnXXX"},
{"data": "ColumnYYY" }
});
As it turns out, I have a problem with the disappearing headers only when I use the DataTable
I don't know how the table data is store but this way is good:
$scope.configurableConfigurations = [{
name: "Locations",
table: "location_set",
columnList: [{
name: "LOCATION_NAME",
alias: "Location",
primary: true
}, {
name: "DESCRIPTION",
alias: "Description",
primary: false
}],
data:[[12,"home"],[43,"work"]]
}, {
name: "System Parameters",
table: "system_params",
columnList: [{
name: "param_Key",
alias: "Parameter",
primary: true
}, {
name: "param_Value",
alias: "Value",
primary: false
}],
data:[["GOO",586],["FOO",123]]
}];
And then you can print the table like this:
<select name="ConfigurationName" ng-model="selected" ng-options="eachConfiguration as eachConfiguration.name for eachConfiguration in configurableConfigurations" ng-change="fetchRequiredConfiguration()"></select>
<table id="configtable">
<thead>
<tr>
<th class="col-md-1" ng-repeat="column in selected.columnList">{{column.alias}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in selected.data" >
<td class="col-md-1" ng-repeat="col in row">{{col}}</td>
</tr>
</tbody>
</table>
Example here

Generate table body rows using ng-repeat

I'm using Smart Table (latest version) and AngularJs (v. 1.2.16) to build a table using two objects, one for the header and the other for the table content. My problem happens when creating the table body cells. Using the following code works fine:
<tr ng-repeat="row in rowCollection">
<td>{{row.productNumber}}</td>
<td>{{row.BuyIt}}</td>
<td>{{row.brand}}</td>
</tr>
But I need to generate the body like this:
<tr ng-repeat="row in rowCollection">
<td ng-repeat="col in columns">{{value-to-show}}</td>
</tr>
My object is this:
$scope.rowCollection = [{
"productNumber": 5877,
"BuyIt": "Online",
"brand": "BrandA"
}, {
"productNumber": 5743,
"BuyIt": "Online",
"brand": "BrandB"
}];
$scope.columns = [{
'colName': 'Product Number',
'Id': 'column1',
'className': '',
'skipNatural': true,
'sortDefault': 'reverse'
}, {
'colName': 'Store or Online',
'Id': 'column2',
'className': '',
'skipNatural': true
}, {
'colName': 'Brand',
'Id': 'column3',
'className': '',
'skipNatural': true
}];
How can I get the right value to appear in the right cell?
I have a jsfiddle that shows the problem: http://plnkr.co/edit/aEfzzU?p=preview
Any help is really appreciated.
You can change your ng-repeat to this:
<tbody>
<tr ng-repeat="row in rowCollection">
<td ng-repeat="(key, value) in row">{{value}}</td>
</tr>
</tbody>
You may also extend your column objects with which property it corresponds with like this:
<tr ng-repeat="row in rowCollection">
<td ng-repeat="col in columns">{{row[col.property]}}</td>
</tr>
This will also have the nice side effects of:
You may in your header use the st-sort directive on col.property to enable sorting
Dynamically change which columns to display
Use your column objects as a configuration for both header and body
Use on complex objects which contains more properties than you care to show!

Categories

Resources