Vue component inside component renders table incorrectly - javascript

I have 2 components.
Table Component
Row Component (Each row in the table)
The table component calls the row component inside the <tbody> tag for every row in the data.
But while rendering the rows are rendered first (outside the table tag) followed by the <table> tag.
See the example below.
Vue.component('single-table-row', {
props: {
row: {
type: Object
}
},
template: `
<tr>
<td>{{row.id}}</td>
<td>{{row.text}}</td>
</tr>
`
});
Vue.component('mytable', {
props: {
tabledata: {
type: Object
}
},
data: function () {
return {
headers: ['Id', 'Text']
}
},
computed: {
table_rows: function () {
return this.tabledata.data.rows;
}
}
});
var app3 = new Vue({
el: '#app-3',
data: {
mydata: {
data: {
rows: [
{
id: 1,
text: 'Sample 1'
},
{
id: 2,
text: 'Sample 2'
},
{
id: 3,
text: 'Sample 3'
}
]
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app-3">
<mytable v-bind:tabledata="mydata" inline-template>
<div id="table_parent">
<table>
<thead>
<tr>
<th v-for="header in headers">{{header}}</th>
</tr>
</thead>
<tbody>
<single-table-row :row=rows v-for="rows in table_rows" :key=rows.id>
</single-table-row>
</tbody>
</table>
</div>
</mytable>
</div>
The output is rendered as :
<div id="table_parent">
<tr>
<td>2</td>
<td>Sample 2</td>
</tr>
<tr>
<td>1</td>
<td>Sample 1</td>
</tr>
<tr>
<td>3</td>
<td>Sample 3</td>
</tr>
<table>
<thead>
<tr>
<th>Id</th>
<th>Text</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
Ideally it should have rendered the row component inside the <tbody> tag.
What am I missing here ?

You need to use <tr is="single-table-row" instead of <single-table-row.
https://v2.vuejs.org/v2/guide/components.html#DOM-Template-Parsing-Caveats
This is because your template is directly in the HTML. The browser will parse it before Vue gets anywhere near it. Certain elements, such as tbody, have restrictions on what child elements they can have. Any elements that are not allowed will be torn out the table. By the time Vue gets involved they've already been moved.
Vue.component('single-table-row', {
props: {
row: {
type: Object
}
},
template: `
<tr>
<td>{{row.id}}</td>
<td>{{row.text}}</td>
</tr>
`
});
Vue.component('mytable', {
props: {
tabledata: {
type: Object
}
},
data: function () {
return {
headers: ['Id', 'Text']
}
},
computed: {
table_rows: function () {
return this.tabledata.data.rows;
}
}
});
var app3 = new Vue({
el: '#app-3',
data: {
mydata: {
data: {
rows: [
{
id: 1,
text: 'Sample 1'
},
{
id: 2,
text: 'Sample 2'
},
{
id: 3,
text: 'Sample 3'
}
]
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app-3">
<mytable v-bind:tabledata="mydata" inline-template>
<div id="table_parent">
<table>
<thead>
<tr>
<th v-for="header in headers">{{header}}</th>
</tr>
</thead>
<tbody>
<tr is="single-table-row" :row=rows v-for="rows in table_rows" :key=rows.id>
</tr>
</tbody>
</table>
</div>
</mytable>
</div>

Related

How can I create a Vue table component with column slots?

I am currently working with a relatively large Vue (Vue 2) project that uses a lot of tables, and I want to create a reusable table component where each column is a child component / slot. Something like this:
<Table :data="data">
<TableColumn field="id" label="ID" />
<TableColumn field="name" label="Name" />
<TableColumn field="date_created" label="Created" />
</Table>
const data = [
{ id: 1, name: 'Foo', date_created: '01.01.2021' },
{ id: 2, name: 'Bar', date_created: '01.01.2021' }
];
Which in turn should output this:
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Foo</td>
<td>01.01.2021</td>
</tr>
<tr>
<td>2</td>
<td>Bar</td>
<td>01.01.2021</td>
</tr>
</tbody>
</table>
We've previously used Buefy, but the vendor size becomes unnecessarily large, as we only use a fraction of the components' functionality - so I want to create a lightweight alternative.
With this data you only need 2 Props, labels and data.
<!-- Component -->
<table>
<thead>
<tr>
<td v-for="(label, labelIndex) in labels" :key="labelIndex">
{{ label.text }}
</td>
</tr>
</thead>
<tbody>
<tr v-for="(item, itemIndex) in data" :key="itemIndex">
<td v-for="(label, labelIndex) in labels" :key="labelIndex">
{{ item[label.field] }}
</td>
</tr>
</tbody>
</table>
// Data and labels
const labels = [
{ text: ID, field: id },
{ text: Name, field: name },
{ text: Created, field: date_created },
]
const data = [
{ id: 1, name: 'Foo', date_created: '01.01.2021' },
{ id: 2, name: 'Bar', date_created: '01.01.2021' }
];
<table-component
:labels="labels"
:data="data"
>
</table-component>
If you need something more complex you can use nested components combined with a named slots for the header or footer of the table (or other options like search).

Vuejs How can I add a class to each row in the table

I am having an html table as follows :
<tbody>
<tr
v-for="employee in employees"
:key="employee.EmployeeId"
#dblclick="rowOnDblClick(employee.EmployeeId)"
#click="rowOnClick(employee.EmployeeId)"
:class="{rowSelected: rowSelected.status}"
>
<td>{{employee.EmployeeCode}}</td>
<td>{{employee.FullName}}</td>
<td>{{employee.GenderName}}</td>
<td style="text-align: center;">{{employee.DateOfBirth|dateofbirth}}</td>
<td>{{employee.PhoneNumber}}</td>
<td :title="employee.Email">{{employee.Email}}</td>
<td>{{employee.PositionName}}</td>
<td>{{employee.DepartmentName}}</td>
<td style="text-align: right;">{{employee.Salary|money}}</td>
<td>{{employee.WorkStatus|status}}</td>
</tr>
</tbody>
Now I want every time I select a line that line is added class isActive
Tks so much
Try like following snippet:
new Vue({
el: '#demo',
data() {
return {
employees: [{EmployeeId: 1, EmployeeCode: 1, FullName: 'aa bb', GenderName: 'm'},
{EmployeeId: 2, EmployeeCode: 2, FullName: 'cc dd', GenderName: 'm'},
{EmployeeId: 3, EmployeeCode: 3, FullName: 'ee ff', GenderName: 'm'}],
selected: null
}
},
methods: {
rowOnClick(id) {
this.selected = id
}
}
})
Vue.config.productionTip = false
Vue.config.devtools = false
.active {
background-color: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<table>
<tbody>
<tr
v-for="employee in employees"
:key="employee.EmployeeId"
#click="rowOnClick(employee.EmployeeId)"
:class="selected === employee.EmployeeId && 'active'"
>
<td>{{employee.EmployeeCode}}</td>
<td>{{employee.FullName}}</td>
<td>{{employee.GenderName}}</td>
<!--<td style="text-align: center;">{{employee.DateOfBirth|dateofbirth}}</td>
<td>{{employee.PhoneNumber}}</td>
<td :title="employee.Email">{{employee.Email}}</td>
<td>{{employee.PositionName}}</td>
<td>{{employee.DepartmentName}}</td>
<td style="text-align: right;">{{employee.Salary|money}}</td>
<td>{{employee.WorkStatus|status}}</td>-->
</tr>
</tbody>
</table>
</div>

I want to create a activate/deactive button in vue

This is my table. As you can see I have added a button to perform an action.
The action needs to change active to not active and not active to active upon clicking.
I cannot seem to find the SQL area that I could access which makes it difficult for me to update the database upon clicking. Any suggestions or help will be highly appreciated.
If there is any way to update the database upon clicking this button and then the new value should also appear in the datatable.
<table class="table" id="myTable">
<thead>
<tr>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="product in filteredProducts" :key="product.id">
<td>{{ product.status }}</td>
<td>
<div class="btn-group" role="group">
<button class="btn btn-secondary" #click="acdcProduct(product.id)">Active/Deactive</button>
</div>
</td>
</tr>
</tbody>
</table>
Here is what I have tried to do so far. Sorry I am new to vue.js
acdcProduct(id) {
this.axios
.acdc(`http://localhost:8000/api/products/${id}`)
let i = this.products.map(data => data.id).indexOf(id);
this.products.status(i, active)
}
Example for vue side, you should also check if database update was succesfull :
new Vue({
el: '#demo',
data() {
return {
products: [
{id: 1, name: 'prod 1', status: false},
{id: 2, name: 'prod 2', status: false},
{id: 3, name: 'prod 3', status: false},
{id: 4, name: 'prod 4', status: false},
{id: 5, name: 'prod 5', status: false},
]
}
},
methods: {
acdcProduct(id) {
/*this.axios
.acdc(`http://localhost:8000/api/products/${id}`)*/
let i = this.products.map(data => data.id).indexOf(id);
this.products[i].status = !this.products[i].status
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<table class="table" id="myTable">
<thead>
<tr>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products" :key="product.id">
<td>{{ product.status ? 'active' : 'deactive' }}</td>
<td>
<div class="btn-group" role="group">
<button class="btn btn-secondary"
#click="acdcProduct(product.id)"
>
Active/Deactive
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
let filteredProducts is an array in first time it has all data of product and after update product data you have a array of update products in response and again update the filteredProducts array with new updated data.
let filteredProducts = [];
acdcProduct(id) {
axios({
method: "PUT",
url: `http://localhost:8000/api/products/${id}`,
}).then((res) => {
filteredProducts = res.data
});
}

Adjust HTML table with content want to displayed, Angular

My page is about showing data table from user on shift indicator.
My dashboard.component.html
<table class="table">
<thead>
<tr>
<th *ngFor="let col of tablePresetColumns">
{{col.content}}
</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of tablePresetData ">
<td *ngFor="let cell of row"> {{cell.content}}</td>
<td *ngFor="let cell of row">
<span class ="dot" [ngClass]="{
'dot-yellow' : cell.content == 'Busy',
'dot-green' : cell.content == 'Idle',
'dot-red' : cell.content == 'Overload'}">
</span>
</td>
</tr>
</tbody>
</table>
My example data :
tablePresetColumns = [{ id: 1, content: "Username" }];
tablePresetData = [
[{ id: 1, content: "Adiet Adiet" }, { id: 2, content: "Idle" }],
[{ id: 1, content: "Andri Irawan" }, { id: 2, content: "Idle" }],
[{ id: 1, content: "Ari Prabudi" }, { id: 2, content: "Idle" }]
];
How should i do to :
removes the status in the page that I want to display, so it just
appear username and color indicator
I've tried to change *ngFor into this (with index 1) :
<td *ngFor="let cell of row"> {{cell.content[1]}}
but it didn't works at all
Try
<td> {{row[0].content}}</td>
instead of
<td *ngFor="let cell of row"> {{cell.content[1]}}
How about this
<table class="table">
<thead>
<tr>
<th *ngFor="let col of tablePresetColumns">{{col.content}}</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of tablePresetData">
<ng-container *ngFor="let cell of row, let i = index">
<td *ngIf="i == 0">{{cell.content}}</td>
<td *ngIf="i == 0">
<span
[ngClass]="{
'dot-yellow' : row[1].content == 'Busy',
'dot-green' : row[1].content == 'Idle',
'dot-red' : row[1].content == 'Overload'}"
>
</span>
</td>
</ng-container>
</tr>
</tbody>
</table>
See here for a live example: https://codesandbox.io/s/7y2r69992j
Note
I think your data structure is a but awkward and un-semantic. It would be better, if your data would look like this:
tablePresetColumns = ["Username", "Status"];
tablePresetData = [
{username: "Adiet Adiet", status: "Idle"},
{username: "Andri Irawan", status: "Busy" },
{username: "Ari Prabudi", status: "Overload" }
];
So you could show the table like this
<table class="table">
<thead>
<tr>
<th *ngFor="let col of tablePresetColumns">{{col}}</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of tablePresetData">
<td>{{ row.username }}</td>
<td>
<span [ngClass]="{
'dot-yellow' : row.status == 'Busy',
'dot-green' : row.status == 'Idle',
'dot-red' : row.status == 'Overload'
}">
</span>
</td>
</tr>
</tbody>
</table>
Much easier to read and maintain.
Also a live example: https://codesandbox.io/s/o27pv052z
You need to use .map on tablePresetData and have some changes on object structure
angular code here:
tablePresetColumns: any = [
{thTitle:"id", thWidth:'30px'},
{thTitle:"Username", thWidth:'160px'},
{thTitle:"Status", thWidth:'100px'},
{thTitle:"", thWidth:'60px'}
];
tablePresetData: any = [
{ id: 1, Username: "Adiet Adiet", status: "Idle" },
{ id: 2, Username: "Andri Irawan", status: "Overload" },
{ id: 3, Username: "Ari Prabudi", status: "Busy" }
];
constructor() {}
ngOnInit() {
this.tablePresetData.map((item: any) => {
if (item.status === "Busy") {
item["className"] = "dot-yellow";
}
if (item.status === "Idle") {
item["className"] = "dot-green";
}
if (item.status === "Overload") {
item["className"] = "dot-red";
}
});
console.log("this.tablePresetData", this.tablePresetData);
}
Html code
<table>
<thead>
<tr>
<th
*ngFor="let tableHd of tablePresetColumns"
[width]="tableHd.thWidth" >
{{tableHd.thTitle}}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let tableCol of tablePresetData">
<td>{{tableCol.id}}</td>
<td>{{tableCol.Username}}</td>
<td>{{tableCol.status}}</td>
<td>
<span [ngClass]='tableCol.className' class="circle"></span>
</td>
</tr>
</tbody>
</table>
CSS Class -
.circle{border-radius: 10px; display: inline-block; height: 10px; width: 10px; }
.dot-yellow{background-color: yellow;}
.dot-green{background-color:green;}
.dot-red{background-color:red;}

Show button on one row table

I have table:
<table class="table table-condensed">
<thead>
<tr>
<th>id</th>
<th>task</th>
<th>date</th>
</tr>
</thead>
<tbody>
<tr v-for="row in tasks">
<td span #mouseover="showButtonFunction" #mouseleave="hideButtonFunction"> {{row.id}}</td>
<td span #mouseover="showButtonFunction" #mouseleave="hideButtonFunction"> {{row.text}}<button v-if="showBatton">Test</button></td>
<td span #mouseover="showButtonFunction" #mouseleave="hideButtonFunction"> {{row.date_current}}</td>
<td span #mouseover="showButtonFunction" #mouseleave="hideButtonFunction"><button v-if="showBatton">Test</button></td>
</tr>
</tbody>
</table>
As intended, the button should appear on the line on which the mouse is hovering.
But now it appears on all visible lines.
Script:
data:{
showBatton:false,
},
showButtonFunction(){
// this.title="dsds2"
console.log("test")
this.showBatton=true;
},
hideButtonFunction(){
this.showBatton=false;
}
How to implement it?
You can do this with css only:
// CSS
tr button {
display: none;
}
tr:hover button {
display: inline-block;
}
// HTML
<tr v-for="row in tasks">
<td span>{{row.id}}</td>
<td span>{{row.text}}<button>Test</button></td>
<td span>{{row.date_current}}</td>
<td span><button>Test</button></td>
</tr>
You can do it Using VueJS Also Like:
<div id="app">
<table class="table table-condensed">
<thead>
<tr>
<th>id</th>
<th>task</th>
<th>date</th>
</tr>
</thead>
<tbody>
<tr v-for="row in tasks" #mouseover="showButtonFunction(row.id)" #mouseleave="hideButtonFunction" :key="row.id">
<td>{{row.id}}</td>
<td>{{row.text}}<button v-show="buttonIndex === row.id">Test</button></td>
<td>{{row.date_current}}</td>
<td><button v-show="buttonIndex === row.id">Test</button></td>
</tr>
</tbody>
</table>
</div>
JS Code:
var vue = new Vue({
el: '#app',
data:{
buttonIndex: false,
tasks: [
{
id: 1,
text: "Hello",
date_current: new Date()
},
{
id: 2,
text: "Hello2",
date_current: new Date()
},
{
id: 3,
text: "Hello3",
date_current: new Date()
}
]
},
methods:{
showButtonFunction(id){
// this.title="dsds2"
this.buttonIndex=id;
},
hideButtonFunction(){
this.buttonIndex=false;
}
}
})
Check this Link :)

Categories

Resources