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

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).

Related

How can I re-render a view after deleting an object from an array, in Svelte?

I am working on a small Svelte application, for learning purposes (Im new to Svelte). The application uses an array of objects displayed in a view as an HTML table:
let countries = [
{ code: "AF", name: "Afghanistan" },
{ code: "AL", name: "Albania" },
{ code: "IL", name: "Israel" }
];
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>Code</th>
<th>Name</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
{#if countries.length}
{#each countries as c, index}
<tr>
<td>{index+1}</td>
<td>{c.code}</td>
<td>{c.name}</td>
<td class="text-right">
<button data-code="{c.code}" on:click="{deleteCountry}" class="btn btn-sm btn-danger">Delete</button>
</td>
</tr>
{/each}
{:else}
<tr>
<td colspan="4">There are no countries</td>
</tr>
{/if}
</tbody>
</table>
I am doing a delete operation this way:
function deleteCountry(){
let ccode = this.getAttribute('data-code');
let itemIdx = countries.findIndex(x => x.code == ccode);
countries.splice(itemIdx,1);
console.log(countries);
}
There is a REPL here.
The problem
I have been unable to render the table (view) again, after the countries array is updated (an element is deleted from it).
How do I do that?
add
countries = countries;
after this line
countries.splice(itemIdx,1);
since reactivity/rerendering/UI update only marked after assignment.
For svelte to pick up the change to your array of countries, you need to create a new reference of the array. For this you could use the Array.filter method.
<script>
let countries = [
{ code: "AF", name: "Afghanistan" },
{ code: "AL", name: "Albania" },
{ code: "IL", name: "Israel" }
];
function deleteCountry(code) {
countries = countries.filter(c => c.code !== code)
}
</script>
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>Code</th>
<th>Name</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
{#if countries.length}
{#each countries as c, index}
<tr>
<td>{index+1}</td>
<td>{c.code}</td>
<td>{c.name}</td>
<td class="text-right">
<button on:click="{() => deleteCountry(c.code)}" class="btn btn-sm btn-danger">Delete</button>
</td>
</tr>
{/each}
{:else}
<tr>
<td colspan="4">There are no countries</td>
</tr>
{/if}
</tbody>
</table>
Also you can directly use the country code as an argument for the deleteCountry method.

Vue component inside component renders table incorrectly

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>

add multiple rows in one column using angular ng-repeat

I want to generate table contents using json object. I am using ng-repeat to insert multiple rows in a table. But I have to generate table like the following.
--------------
ID | subjects
--------------
| S1
1 | S2
| S3
--------------
| S4
2 | S5
| S6
--------------
my angular code is:
<tr ng-repeat = "user in users">
<td>{{user.id}}</td>
<td>{{user.subject}}</td>
</tr>
my json object is :
user:[
{id:1 ,
subjects:[
{id:1 , name:"eng"}
{id:2 , name:"phy"}
]
},
{id:2 ,
subjects:[
{id:1 , name:"eng"}
{id:3 , name:"math"}
]
}
]
I want to generate html table like this
<table>
<tr>
<td >ID</td>
<td>Sub</td>
</tr>
<tr>
<td rowspan="3">1</td>
<td>S1</td>
</tr>
<tr>
<td>S2</td>
</tr>
<tr>
<td>S3</td>
</tr>
how to insert multiple rows in one column using angular
Use ng-repeat-start and ng-repeat-end. For example:
<tr ng-repeat-start="user in users">
<td rowspan="{{user.subjects.length+1}}">{{user.id}}</td>
</tr>
<tr ng-repeat-end ng-repeat="subject in user.subjects">
<td>S{{subject.id}}</td>
</tr>
Here is a full example:
var app = angular.module('MyApp', []);
app.controller('MyController', function ($scope) {
var users = [{
id: 1,
subjects: [{
id: 1,
name: "eng"
}, {
id: 2,
name: "phy"
}]
}, {
id: 2,
subjects: [{
id: 1,
name: "eng"
}, {
id: 3,
name: "math"
}, {
id: 4,
name: "hist"
},
{
id: 5,
name: "geog"
}]
}];
$scope.users = users;
});
table { border-collapse: collapse; }
td { border: 1px solid Black; }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<table ng-app="MyApp" ng-controller="MyController">
<thead>
<tr>
<td>ID</td>
<td>Subjects</td>
</tr>
</thead>
<tbody>
<tr ng-repeat-start="user in users">
<td rowspan="{{user.subjects.length+1}}">{{user.id}}</td>
</tr>
<tr ng-repeat-end ng-repeat="subject in user.subjects">
<td>S{{subject.id}}</td>
</tr>
</tbody>
</table>
Also, working fiddle here: http://jsfiddle.net/donal/r51d5fw5/17/
An alternative version, using nested ng-repeat, can be implemented using a div to display the nested subject information:
<tr ng-repeat="user in users">
<td>{{user.id}}</td>
<td>
<div ng-repeat="subject in user.subjects">S{{subject.id}}</div>
</td>
</tr>
rowspan will do everything you need. Follow this link:
w3schools This is one of the best resources for web development.
Using div u can also do
<div ng-repeat = "user in users">
<div> {{user.id}} </div>
<div ng-repeat = "user1 in users.subjects">
{{user1.id}} : {{user1.name}}
<div>
</div>
Donal's answer is good.
But I edited to show beautiful table:
<table class="table table-bordered table-hover table-height m-b-xs">
<thead>
<tr>
<td>ID</td>
<td>Subjects</td>
<td>name</td>
</tr>
</thead>
<tbody>
<tr ng-repeat-start="user in users">
<td rowspan="{{user.subjects.length}}">key {{user.id}}</td>
<td>S{{ user.subjects[0].id }}</td>
<td>{{ user.subjects[0].name }}</td>
</tr>
<tr ng-repeat-end ng-repeat="subject in user.subjects.slice(1)">
<td>S{{subject.id}}</td>
<td>{{subject.name}}</td>
</tr>
</tbody>

Generate html table using angular

I want to generate html table using angular.I have a json object and i used ng-repeat to add multiple rows. But my table structure is different.
I want to generate table structure like this:
<table>
<tr>
<td >ID</td>
<td>Sub</td>
</tr>
<tr>
<td rowspan="3">1</td>
<td>S1</td>
</tr>
<tr>
<td>S2</td>
</tr>
<tr>
<td>S3</td>
</tr>
</table>
--------------
ID | subjects
--------------
| S1
--------
1 | S2
--------
| S3
--------------
| S4
--------
2 | S5
--------
| S6
--------------
user:[
{id:1 ,
subjects:[
{id:1 , name:"eng"}
{id:2 , name:"phy"}
]
},
{ id:2 ,
subjects:[
{id:1 , name:"eng"}
{id:3 , name:"math"}
]
}
]
my angular code is:
<tr ng-repeat = "user in users">
<td>{{user.id}}</td>
<td>{{user.subject}}</td>
</tr>
I want to know how to generate table structure like above
You need to use ng-repeat-start and ng-repeat-end. For example:
var app = angular.module('MyApp', []);
app.controller('MyController', function ($scope) {
var users = [{
id: 1,
subjects: [{
id: 1,
name: "eng"
}, {
id: 2,
name: "phy"
}]
}, {
id: 2,
subjects: [{
id: 1,
name: "eng"
}, {
id: 3,
name: "math"
}]
}];
$scope.users = users;
});
table { border-collapse: collapse; }
td { border: 1px solid Black; }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<table ng-app="MyApp" ng-controller="MyController">
<thead>
<tr>
<td>ID</td>
<td>Subjects</td>
</tr>
</thead>
<tbody>
<tr ng-repeat-start="user in users">
<td rowspan="{{user.subjects.length+1}}">{{user.id}}</td>
</tr>
<tr ng-repeat-end ng-repeat="subject in user.subjects">
<td>S{{subject.id}}</td>
</tr>
</tbody>
</table>
Also, working fiddle here: http://jsfiddle.net/donal/r51d5fw5/17/
Similar to the nested structure of your object, you can nest your ng-repeats like this...
<tr ng-repeat = "user in users">
<td>{{user.id}}</td>
<td ng-repeat="subject in user.subjects">{{subject.name}}</td>
</tr>
I split your JSON and push it into two $scope variable, like the below:
angular.forEach($scope.user, function(user) {
$scope.userDetails.push({
userId: user.id,
});
angular.forEach(user.subjects, function(subject) {
$scope.subjectDetails.push({
userId: user.id,
subjectName: subject.name
});
});
});
In the HTML, I am using filter to filter by user's id.
<table>
<thead>
<tr>
<th style="width: 50px">ID</th>
<th style="width: 75px">Subjects</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in userDetails">
<td><span ng-bind="user.userId"></span>
</td>
<td>
<table>
<tr ng-repeat="sub in subjectDetails | filter: {userId: user.userId}">
<td>
<div ng-bind="sub.subjectName"></div>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
This is also one of the way to achieve this.
Sample Plunker
Here, I'm assuming that you want each user in users (in your JSON object) in one table, so you can do something as follows:
<table ng-repeat="user in users">
<tr>
<td>{{user.id}}</td>
<td>Subjects</td>
</tr>
<tr ng-repeat="subject in user.subjects">
<td>{{subject.id}}</td>
<td>{{subject.name}}</td>
</tr>
</table>
Adjust the html accordingly based on your needs, but this is the basic idea :)
Hope this helps!
add users object in $scope.
$scope.users=[
{id:1 ,
subjects:[
{id:1 , name:"eng"}
{id:2 , name:"phy"}
]
},
{ id:2 ,
subjects:[
{id:1 , name:"eng"}
{id:3 , name:"math"}
]
}
]
Here is the html.
<table border="1">
<tr>
<th> ID </th>
<th> subjects </th>
</tr>
<tr ng-repeat="user in users">
<td>{{user.id}}</td>
<td><table>
<tr ng-repeat="subject in user.subjects">
<td>{{subject.name}}</td>
</tr>
</table></td>
</tr>
</table>
Here is the plunker

"with" databinding on a multi-dimensional array in Knockout Js

I have an array that looks something like the following
var initData = [
new Order({
orderId: "183175",
name: "Columbus Africentric",
production: [{
pType: "Art Time",
by: "MJ"
}, {
pType: "Front Pocket",
by: "WB"
}]
}),
new Order({
orderId: "198675",
name: "Stanford High",
production: [{
pType: "Art Time",
by: "MJ"
}, {
pType: "Full Back",
by: "WB"
}]
})
]
I'm trying to do a with binding to show only extra information for on order when the item is clicked on. So I have a foreach for the orders that shows the orderId and the name in a table, and a button to click that then should show all production items for the chosen order. Something like the following
<tbody data-bind="foreach:orders">
<tr>
<td>
<label class="read" data-bind="text:orderId, visible:true" />
</td>
<td>
<label class="read" data-bind="text:name, visible:!$root.isItemEditing($data)" />
</td>
<td>
<td class="tools">
<div data-bind="if: production"><button data-bind="click: $root.toggleProductionMode">Production</button>
</div>
</td>
</tr>
<tr data-bind="visible: showProductionOrder, with: production">
<td colspan="5">
<h3>Production Summary</h3>
<table class="ko-grid">
<thead>
<tr>
<th>Type</th>
<th>By</th>
</tr>
</thead>
<tbody
<tr>
<td>
<label class="read" data-bind="text:pType, visible:!$root.isItemEditing($data)" />
</td>
<td>
<label class="read" data-bind="text:by, visible:!$root.isItemEditing($data)" />
</td>
</tr>
</tbody>
</table>
</tr>
I think I need to use a foreach to get to the production information. Can a foreach binding be used inside a with binding? Or do I even need one? If I have it bound using the "with" binding, is there a certain way to get to the multiple production items? I know this is super easy and it's probably staring me right in the face.
also, when creating the Item Model I'm doing the following, which I think might be incorrect.
function Order(data) {
self.orderId = ko.observable();
self.name = ko.observable();
self.production = ko.observableArray([
[
self.pType = ko.observable(),
self.by = ko.observable()
]
]);
}
You don't have to create a new table inside the main table. For a child collection you have to use "ko foreach: production" as a html comment and then add your tr tags afterwards to display the production items. Have a look into this JSFiddle example.
// HTML
<table>
<tr>
<th>Student ID</th>
<th>Student Name</th>
</tr>
<tbody data-bind="foreach: Students">
<tr>
<td data-bind="text: StudentID"></td>
<td data-bind="text: StudentName"></td>
</tr>
<!-- ko foreach: Courses -->
<tr>
<td style='padding-left:20px;' data-bind="text: CourseID"></td>
<td style='padding-left:20px;' data-bind="text: CourseName"></td>
</tr>
<!-- /ko -->
</tbody>
</table>
// KNOCKOUT CODE
function StudentViewModel() {
var self = this;
self.Students = [
{ StudentID: "1", StudentName: "Ali",
Courses: [ { CourseID: "100", CourseName: "Math" }, { CourseID: "102", CourseName: "Physics" } ]
},
{ StudentID: "2", StudentName: "Isa" ,
Courses: [ { CourseID: "103", CourseName: "Chemistry" }, { CourseID: "104", CourseName: "Social Studies" } ] },
{ StudentID: "3", StudentName: "Zoya" ,
Courses: [ { CourseID: "100", CourseName: "Math" }, { CourseID: "106", CourseName: "Stats" } ] },
];
}
ko.applyBindings(new StudentViewModel());

Categories

Resources