I need to create a table from some data with multiple levels of arrays and sub-arrays. I have found some solutions to do this as long as I only have two levels of arrays, but none that would work with anything more than that.
For example, take the sample data below --
$scope.professors = [{
'name': 'Albert Einstein',
'classes': [{
'name': 'Physics 101',
'students': [{
'name': 'Joe',
'grade': 'B'
}, {
'name': 'Mary',
'grade': 'A'
}]
}, {
'name': 'Physics 201',
'students': [{
'name': 'Gunther',
'grade': 'C'
}, {
'name': 'Hans',
'grade': 'C'
}]
}]
}, {
'name': 'Charles Darwin',
'classes': [{
'name': 'Biololgy 101',
'students': [{
'name': 'Danielle',
'grade': 'A'
}, {
'name': 'Anne',
'grade': 'A'
}]
}, {
'name': 'Biology 201',
'students': [{
'name': 'Frida',
'grade': 'A'
}, {
'name': 'Fritz',
'grade': 'F'
}]
}]
}];
You have some professors each with some disciplines each in turn with some students enrolled. I want to create a table-report that show all professors along with all their disciplines and students and grades. In order to do this, I would need a table such as the one below --
<table>
<tbody>
<tr>
<th rowspan="4">Albert Einstein</th>
<th rowspan="2">Physics 101</th>
<td>Joe</td>
<td>B</td>
</tr>
<tr>
<td>Mary</td>
<td>A</td>
</tr>
<tr>
<th rowspan="2">Physics 201</th>
<td>Gunther</td>
<td>C</td>
</tr>
<tr>
<td>Hans</td>
<td>C</td>
</tr>
<tr>
<th rowspan="4">Charles Darwin</th>
<th rowspan="2">Biology 101</th>
<td>Danielle</td>
<td>A</td>
</tr>
<tr>
<td>Anne</td>
<td>A</td>
</tr>
<tr>
<th rowspan="2">Biology 201</th>
<td>Frida</td>
<td>A</td>
</tr>
<tr>
<td>Fritz</td>
<td>F</td>
</tr>
</tbody>
</table>
i.e.
|------------------|-------------|----------|---|
| Albert Einstein | Physics 101 | Joe | B |
| | | Mary | A |
| | ------------|----------|---|
| | Physics 201 | Gunther | C |
| | | Hans | C |
|------------------|-------------|----------|---|
| Charles Darwin | Biology 101 | Danielle | A |
| | | Anne | A |
| |-------------|----------|---|
| | Biology 201 | Frida | A |
| | | Fritz | F |
|------------------|-------------|----------|---|
The solutions I've found generate multiple tbody elements with ng-repeat for each professor (in this case) and then another ng-repeat on a tr for each "class" for that professor. Like --
<table>
<tbody ng-repeat="prof in professors">
<tr ng-repeat="c in prof.classes">
<th ng-if="$first" rowspan="{{prof.classes.length}}">{{prof.name}}</th>
<td>{{c.name}}</td>
</tr>
</tbody>
</table>
Others use ng-repeat-start and ng-repeat-end but none more than two levels deep. Is there a way to add one more level—in this case, the students—to this?
After much frustration and head scratching, I found a way to do it using ng-repeat-start
<table>
<tbody>
<tr ng-repeat-start="p in professors" ng-if="false"></tr>
<tr ng-repeat-start="c in p.classes" ng-if="false"></tr>
<tr ng-repeat="s in c.students">
<th ng-if="$parent.$first && $first" rowspan="{{p.count}}">
{{p.name}}
</th>
<th ng-if="$first" rowspan="{{c.students.length}}">{{c.name}}</th>
<td>{{s.name}}</td>
<td>{{s.grade}}</td>
</tr>
<tr ng-repeat-end ng-if="false"></tr> <!-- classes -->
<tr ng-repeat-end ng-if="false"></tr> <!-- professors -->
</tbody>
</table>
This solution generates valid HTML table markup. By using ng-if="false" on the ng-repeat-* elements, they iterate through the arrays but generate no actual element on the page. The only elements generated are the tr rows in s in c.students.
The other thing I needed to do is create a new property inside the professor objects named count that holds the total number of students enrolled in all the classes. I needed that for the rowspan and it's of course trivial to generate on the fly when I get data from the server.
Related
I just want to copy data(Item and Price) of the first table to the second table and make increment in Quantity(Second table) when i click in the First Table ( Item). I am working on Angular 9
First Table:
Code | Item | Price | Unit |
1 | Mouse | 500 | Piece |
2 | Wire | 100 | Piece |
Second Table:
Item | Quantity| Price |
1 | 2 | 1000 |
2 | 1 | 100 |
I did a lot to work with it but I could not get any required output.
It is possible to solve using javaScript/Js/jquery
<table class="table table-hover" id="firstTable">
<thead class="table-primary">
<tr>
<td>Code</td>
<td>Item</td>
<td>Unit</td>
<td>Price</td>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of itemList;" id="moveMeIntoMain"
style="cursor:pointer">
<td>{{data.itemCode}}</td>
<td>{{data.itemName}}</td>
<td><span>{{data.unitId}}</span>{{data.name}}</td>
<td>{{data.retailRate}}</td>
</tr>
</tbody>
</table>
<table class="table table-hover" id="secondTable">
<thead class="">
<tr class="table-primary">
<td>#</td>
<td>Item</td>
<td>Price</td>
<td>Quantity</td>
<td>Action</td>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
Js
$(document).ready(function () {
var items = [];
$("#firstTable tr").on("click", function () {
var newTr = $(this).closest("tr").clone();
$(newButtonHTML).children("button").click(function (e) {
});
$(newTr).children("td:last").html("");
items.push(newTr);
newTr.appendTo($("#secondTable"));
});
})
You can simply try in Angular way ;). Just add click handler to the each row and send row data
Working stackblitz
Template file
<table>
<thead>
<th>Code</th>
<th>Item</th>
<th>Price</th>
<th>Unit</th>
</thead>
<tr *ngFor="let data of firstTableData" (click)="updateSecondTable(data)">
<td>{{ data.Code }}</td>
<td>{{ data.Item }}</td>
<td>{{ data.Price }}</td>
<td>{{ data.Unit }}</td>
</tr>
</table>
<hr>
<table *ngIf="secondTableData.length">
<thead>
<th>Item</th>
<th>Quantity</th>
<th>Price</th>
</thead>
<tbody>
<tr *ngFor="let newdata of secondTableData">
<td>{{ newdata.Item }}</td>
<td>{{ newdata.Quantity }}</td>
<td>{{ newdata.Price }}</td>
</tr>
</tbody>
</table>
And in the typescript file
Declare a empty secondTableData array
And have a method, which firstly checks for the item existence in the second table array. If item already exists, then simply update quantity and price
If item doesn't exist, just push the item with required columns to the second table array
Component.ts
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name = 'Angular ' + VERSION.major;
firstTableData = [{
Code: 1,
Item: 'Mouse',
Price: 500,
Unit: 'Piece'
},
{
Code: 2,
Item: 'Wire',
Price: 100,
Unit: 'Piece'
},
{
Code: 3,
Item: 'Some Item',
Price: 300,
Unit: 'Some Unit'
}
];
secondTableData = [];
updateSecondTable(data) {
let foundItem = this.secondTableData.find((item) => item.Item === data.Item);
if (foundItem) {
foundItem.Quantity += 1;
foundItem.Price += data.Price;
return;
}
this.secondTableData.push({
Item: data.Item,
Quantity: 1,
Price: data.Price,
})
}
}
I have an array which is created from splitting a string using str.split() and this creates an array and it looks like this:
var array = [
"TEXT1,1234,4321,9876",
"TEXT2,2345,5432",
"TEXT3,6543,3456"
]
I want to be able to display this information in a table using *ngFor.
I have tried this (from this question):
<tr>
<td *ngFor="let info of array">{{info.join(",")}}</td>
</tr>
But I get this error:
ERROR TypeError: _v.context.$implicit.join is not a function
at Object.eval [as updateRenderer]
The table should look like this:
TITLE | VALUE1 | VALUE2 | VALUE3
--------------------------------
TEXT1 | 1234 | 4321 | 9876
--------------------------------
TEXT2 | 2345 | 5432 |
--------------------------------
TEXT3 | 6543 | 3456 |
How do I achieve this table?
To make array from string with , delimiters use String.prototype.split method
<tr *ngFor="let row of array">
<td *ngFor="let info of row.split(',')">{{info}}</td>
</tr>
Ng-run Example
You will need two *ngFors. One to loop through each item in the array and one to iterate through each item in the array that you'll create by splitting the string with ,
Give this a try:
<table border="1">
<thead>
<tr>
<td>TITLE</td>
<td>VALUE 1</td>
<td>VALUE 2</td>
<td>VALUE 3</td>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of array">
<td *ngFor="let column of row.split(',')">
{{ column }}
</td>
</tr>
</tbody>
</table>
Here's a Working Sample StackBlitz for your ref.
I am struggling with this issue for a while and really appreciate if someone can give some idea on how I can move forward with.
The idea is to reduce the number of rows in the table by grouping the relevant data in the below format.
I need to convert the JSON response coming from server (non-grouped data) to a particular format so that I can paint the table with the grouped data.
**Initial Layout**
+------------+-----------+--------+------------+
| Category | Product | Size | Shipping
+------------+-----------+--------+------------+
| Category 1 | Product 1 | Big | Free |
| Category 1 | Product 2 | Small | Free |
| Category 1 | Product 3 | Big | Free |
| Category 1 | Product 4 | Small | Free |
+------------+-----------+--------+-------------
**Expected Result**
+------------+----------------------+--------+-----------
| Category | Product | Size | Shipping
+------------+----------------------+--------+------------
| Category 1 | Product 1, Product 3 | Big | Free |
| | Product 2, Product 4 | Small | Free |
+------------+----------------------+--------+----------
More details in the jsfiddle:
https://jsfiddle.net/zy8kj2tb/2/
// please see the code in jsfiddle
<h3>
Initial layout:
</h3>
<h6>
<p>
Category = Category 1, Category 2, etc or "blank"
</p>
<p>
Product = Product 1, Product 2, etc or "blank"
</p>
<p>
Size = Big, Small, XL, etc or "blank"
</p>
<p>
Shipping = Free, Standard, Expedited, etc or "blank"
</p>
<p>
dont group blank rows with cat, prod, size = make it as individual row
</p>
</h6>
<div id="initialLayout">
</div>
<h3>Expected result:</h3>
<table id="expectedTable" border="1" cellpadding="3" cellspacing="0">
<tr>
<th>Category</th>
<th>Product</th>
<th>Size</th>
<th>Shipping</th>
</tr>
<tr>
<td rowspan=2>Category 1</td>
<td rowspan="2">Product 1, Product 3 <br>Product 2, Product 4</td>
<td rowspan="2">Big <br>Small</td>
<td rowspan="2">Free</td>
</tr>
</table>
<br/>
Just replace your HTML with this one, I did two things -
Changed first row with -
<td rowspan="2">Product 1, Product 3 <br>Product 2, Product 4</td>
<td rowspan="2">Big <br>Small
</td>
Removed respective td tags from the second row
I just looked at your code
where you have written
<td rowspan=2>Category 1</td>
<td>Product 1, Product 3</td>
<td>Big</td>
<td rowspan=2>Free</td>
It should be
<td rowspan="2">Category 1</td>
<td>Product 1, Product 3</td>
<td>Big</td>
<td rowspan="2">Free</td>
you have just missed some quotes
Here's the data I'm working with:
$scope.products = [
{
'id': 643,
'name': 'Product Name',
'applications': [
{
'id': 100,
'name': 'Adobe After Effects CC (2014)',
'gfx': [
{
'id': 514,
'name': 'Graphics AE Test 1'
}
]
},
{
'id': 101,
'name': 'Adobe Premiere Pro CC (2014)',
'gfx': [
{
'id': 514,
'name': 'Graphics AP Test 1'
},
{
'id': 512,
'name': 'Graphics AP Test 2'
}
]
}
]
}
];
What i'm trying to do is loop through all of the graphics card for a specific application. You choose the application via a dropdown, and I have the selected application in a variable {{ scope.selectedApplication }}. So with that, here's what I have tried:
<tr data-ng-repeat="driver in result.applications | filter: { name: selectedApplication } track by $index">
<td>{{ driver.gfx[$index].name }}</td>
</tr>
So this is somewhat working, just not exactly how I want. The filter is filtering it down to the correct application, which works fine. The problem I am having is that driver.gfx[$index].name is only showing the first result. Since I am looping through applications instead of gfx, the $index isn't going to work for me.
How can I loop through the graphics cards after my initial ng-repeat? It seems like I need two statements, but how would that work?
Am I going about this the wrong way?
you can have nested hg-repeat if you like to have a nested table.
<tr data-ng-repeat="driver in result.applications | filter: { name: selectedApplication } track by $index">
<td>
<table>
<tr ng-repeat="item in driver.gfx">
<td >
{{item.name}}
<td/>
</tr>
</table>
</td>
</tr>
if you want to have single denormalized table, one option would be to create a function that does the denormalization and then use result of that function in a normal hg-repeat.
The other option would be having multiple tbody. so your outer loop occurs on tbody and inner loop on row
<tbody data-ng-repeat="driver in result.applications | filter: { name: selectedApplication } track by $index">
<tr ng-repeat="item in driver.gfx">
<td >
{{item.name}}
<td/>
</tr>
</tbody>
and finally you can have some rows that either style them as separators or just hide them by CSS and use ng-repeat-start and hg-repeat-end like this
<table>
<tr class="separator" data-ng-repeat-start="driver in result.applications | filter: { name: selectedApplication } track by $index"><td></td></tr>
<tr ng-repeat="item in driver.gfx">
<td >
{{item.name}}
<td/>
</tr>
<tr class="seperator" ng-repeat-end><td></td></tr>
</table>
You need another ng-repeat that loops through each gfx.
<td>
<ul>
<li ng-repeat="gfx in driver.gfx">{{ gfx.name }}</li>
</ul>
</td>
Assuming the selected application is set via a binding of {{ selectedApplication }} ($scope is implied), then your ng-repeat should look like:
<tr data-ng-repeat="driver in selectedApplication | filter: { name: selectedApplication } track by $index">
<td>{{ driver.gfx[$index].name }}</td>
</tr>
This means you'll be talking about the driver inside the selected application object.
This problem's been haunting me for days... I have the following JSON that I'm trying to render as a table using Tempo.js but keep getting nothing rendered:
[
{
"id":"xxxxxxxxxxxxxxxxxxxxxxxxxx",
"name":"Family One",
"parents":[
{
"id":"yyyyyyyyyyyyyyyyyyyyyyyyyy",
"name":"John Doe",
"children":[
{
"id":"zzzzzzzzzzzzzzzzzzzzzzzzz",
"name":"Fabrice A",
"description":"Good kid",
"Age":"20",
"Weight":"60"
}
]
},
{
"id":"hhhhhhhhhhhhhhhhhhhhhhhhhh",
"name":"Jane Doe",
"children":[
{
"id":"wwwwwwwwwwwwwwwwwwwwwwww",
"name":"Maurice A",
"description":"Bad kid",
"Age":"22",
"Weight":"50"
}
]
}
]
},
{
"id":"xxxxxxxxxxxxxxxxxxxxxxxxxx2",
"name":"Family Two",
"parents":[
{
"id":"yyyyyyyyyyyyyyyyyyyyyyyyyy2",
"name":"Sonny Doe",
"children":[
{
"id":"zzzzzzzzzzzzzzzzzzzzzzzzz2",
"name":"Juan B",
"description":"Good kid",
"Age":"30",
"Weight":"70"
},
{
"id":"zzzzzzzzzzzzzzzzzzzzzzzzz3",
"name":"Alberto B",
"description":"Fine kid",
"Age":"20",
"Weight":"60"
},
{
"id":"zzzzzzzzzzzzzzzzzzzzzzzzz4",
"name":"Roberto B",
"description":"Medium kid",
"Age":"10",
"Weight":"50"
}
]
}
]
}
]
The table is supposed to look like this:
_______________________________________________________________________________________
| FAMILY NAME | PARENTS | CHILD NAME | CHILD DESCRIPTION | CHILD AGE | CHILD WEIGHT |
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
| Family One | John Doe | Fabrice A | Good kid | 20 | 60 |
| |''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
| | Jane Doe | Maurice A | Bad kid | 22 | 50 |
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
| Family Two | Sonny Doe | Juan B | Good kid | 30 | 70 |
| | | Alberto B | Fine kid | 20 | 60 |
| | | Roberto B | Medium kid | 10 | 50 |
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Please notice how the family cell stretches to house more than one parent rows, and the parent cell streches to house more than one children rows.
I prepare the JSON in js in a variable called familyTree, and then call Tempo.prepare('family-list').render(familyTree);
No I've read all of the documentation for tempo.js (which wasn't too long), but I still haven't found a way of rendering the table properly. Here's what I got so far:
<div id="family-list">
<table id="families">
<tr data-before-template='data-before-template'>
<th>
FAMILY NAME
</th>
<th>
PARENTS
</th>
<th>
CHILD NAME
</th>
<th>
CHILD DESCRIPTION
</th>
<th>
CHILD AGE
</th>
<th>
CHILD WEIGHT
</th>
</tr>
<tr data-template='data-template'>
<td id="family-column">
{{name}}
</td>
<td id="parent-column" data-template-for='parents'>
{{name}}
</td>
<td colspan="4">
<table id='children-table' data-template-for="children">
<tr>
<td>
{{name}}
</td>
<td>
{{description}}
</td>
<td>
{{age}}
</td>
<td>
{{weight}}
</td>
</tr>
</table>
</td>
</tr>
I've used tempo before. I've even mixed it with wkhtmltopdf to render some nice-looking pdf files. But I just cannot solve this. If any of you out there has been through anything similar..would you please share your experience? Thanks so much in advance.
Using tempo.js is not ideal to render hierarchical data in a table. To render hierarchical data, tempo.js requires that the HTML elements also exists in a hierarchy. This is not ideal when dealing with tables, because creatign a table inside a table cell will eventually lead to column alignment issues. You can take care of alignment issues to a certain degree by fixing the width of each column, but this will not be a perfect solution.
Having said above, I've fixed your HTML markup to render your JSON data using tempo.js. See the changes below (jsfiddle here):
<div id="family-list">
<table id="families">
<tr data-before-template='data-before-template'>
<th width="100px">
FAMILY NAME
</th>
<th width="100px">
PARENTS
</th>
<th width="100px">
CHILD NAME
</th>
<th width="150px">
CHILD DESCRIPTION
</th>
<th width="50px">
CHILD AGE
</th>
<th width="50px">
CHILD WEIGHT
</th>
</tr>
<tr data-template='data-template'>
<td id="family-column">
{{name}}
</td>
<td colspan="5">
<table cellpadding="0">
<tr data-template-for='parents'>
<td width="100px">
{{name}}
</td>
<td>
<table id='children-table' cellpadding="0">
<tr data-template-for="children">
<td width="100px">
{{name}}
</td>
<td width="150px">
{{description}}
</td>
<td width="50px">
{{Age}}
</td>
<td width="50px">
{{Weight}}
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>