ERROR Error: dataKey must be defined to use row expansion - html

I'm trying to figure out how to use Row Expansion in PrimeEng. I was just following the guide in their documentation and it shows that I should define my datakey. I already defined it below if I'm not mistaken. I'm new to this, please don't judge.
<p-table [value]="users" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries"
[rowsPerPageOptions]="[10, 25, 50]">
<ng-template pTemplate="header">
<tr>
<th style="width:2rem">blank</th>
<th style="width:1rem">ID</th>
<th style="width:8rem">Name</th>
<th style="width:8rem">Username</th>
<th style="width:8rem">Email</th>
<th style="width:8rem">Street</th>
<th style="width:8rem">Suite</th>
<th style="width:8rem">City</th>
<th style="width:8rem">Zip code</th>
<th style="width:8rem">LAT</th>
<th style="width:8rem">LNG</th>
<th style="width:8rem">Phone</th>
<th style="width:8rem">Website</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-user let-expanded="expanded">
<tr>
<td>
<!-- <button type="button" pButton pRipple (click)="viewPostUser(user.id)" class="p-button-text p-button-rounded p-button-plain" ></button> -->
<button type="button" pButton pRipple [pRowToggler]="posts" (click)="viewPostUser(user.id)" class="p-button-text p-button-rounded p-button-plain" [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
</td>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.username}}</td>
<td>{{user.email}}</td>
<td>{{user.address.street}}</td>
<td>{{user.address.suite}}</td>
<td>{{user.address.city}}</td>
<td>{{user.address.zipcode}}</td>
<td>{{user.address.geo.lat}}</td>
<td>{{user.address.geo.lng}}</td>
<td>{{user.phone}}</td>
<td>{{user.website}}</td>
</tr>
</ng-template>
Here is my row expansion ng-template.
<ng-template pTemplate="rowexpansion" let-posts>
<tr>
<td colspan="7">
<div class="p-p-3">
<p-table [value]="posts.userId" [dataKey]="posts.userId">
<ng-template pTemplate="header">
<tr>
<th style="width:4rem">userID</th>
<th style="width:4rem">ID</th>
<th style="width:4rem">Title</th>
<th style="width:4rem">Body</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-posts>
<tr>
<td>{{posts.userId}}</td>
<td>{{posts.id}}</td>
<td>{{posts.title}}</td>
<td>{{posts.body}}</td>
<td><p-button type="button" icon="pi pi-search"></p-button></td>
</tr>
</ng-template>
</p-table>
</div>
</td>
</tr>
</ng-template>
I've tried searching the error that I have, but it points me to updating the version.

I think here is where you get it wrong:
[value]="posts.userId" [dataKey]="posts.userId"
[value] is the collection that you want to iterate through, which should be posts in this case
[dataKey] is the property of one item in that collection which you could just define by name: userId
So the final code is:
[value]="posts" [dataKey]="userId"

First, to fix the error you're seeing, add the attribute dataKey="id" to the p-table element.
Next, using let-posts is probably not what you wanted. You didn't write how your data structure is constructed but I'll assume each user has a property called posts. You need to change your template like this:
<ng-template pTemplate="rowexpansion" let-user>
<tr>
<td colspan="7">
<div class="p-p-3">
<p-table [value]="user.posts" dataKey="id">
<ng-template pTemplate="header">
<tr>
<th style="width:4rem">userID</th>
<th style="width:4rem">ID</th>
<th style="width:4rem">Title</th>
<th style="width:4rem">Body</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-post>
<tr>
<td>{{post.userId}}</td>
<td>{{post.id}}</td>
<td>{{post.title}}</td>
<td>{{post.body}}</td>
<td><p-button type="button" icon="pi pi-search"></p-button></td>
</tr>
</ng-template>
</p-table>
</div>
</td>
</tr>
</ng-template>
Explanation:
Use let-user in the rowexpansion template. Now the user variable will contain the user data for the current expandable row.
In the inner p-table, provide the [value] input with the data for the sub table, i.e., the posts for the current user.
In the inner p-table, provide the dataKey input with the name of the field which is the unique key of the table. This might be even not necessary in your case.
In the inner p-table, in the body template, use let-post instead of let-posts since each iteration, i.e., each row of the inner table showing posts for the user, will store the current row, which is a single post.
In each <td> element in the inner table's body template, use post.<field-name>. post will contain the data for the current row so all you need to do is access the field you need inside the object.

You can add dataKey in first line, like this
<p-table [value]="users" dataKey="name">
Maybe it will solve your problem.

Related

Applying an ngClass on only 1 element of a table in angular

In my table, I'm attempting to add a read more button to each row that has a paragraph. The button is displayed for each row, and when I click one, it operates concurrently for each row.
Here is my code:
<table class="table">
<thead>
<tr>
<th scope="col">Text</th>
<th scope="col">Date</th>
<th scope="col">Comments</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let p of posts[0].posts">
<div [ngClass]="{'limitTextHeight': isReadMore}">
<td>{{p.post_text}}</td>
</div>
<button type="button" (click)="showText()" style="margin-top:15px">
{{ isReadMore ? 'Read More': 'Read Less' }}
</button>
<td>{{p.post_date}}</td>
<td>{{p.post_comments.length}} comment</td>
</tr>
</tbody>
</table>
and my showText() function:
showText() {
this.isReadMore = !this.isReadMore
}
This is happening because you're using single boolean variable i.e, isReadMore for all the looped tr tag.
What you can do is posts[0].posts map this posts with one more key that can be anything for example isReadMore in your case, then you will get unique instance to handle for each para, like this.
{{ p?.isReadMore ? 'Read More': 'Read Less' }}.
Hope you understood what I'm trying to say.
You are using a common variable for all the rows. That's why when you click one they all change. You should use local ones, one for each row like so:
<table class="table">
<thead>
<tr>
<th scope="col">Text</th>
<th scope="col">Date</th>
<th scope="col">Comments</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let p of posts[0].posts">
<div [ngClass]="{'limitTextHeight': isReadMore}">
<td>{{p.post_text}}</td>
</div>
<button type="button" (click)="showText(p)" style="margin-top:15px">
{{ p.isReadMore ? 'Read More': 'Read Less' }}
</button>
<td>{{p.post_date}}</td>
<td>{{p.post_comments.length}} comment</td>
</tr>
</tbody>
</table>
and the showText() function:
showText(post) {
post.isReadMore = !post.isReadMore
}
You could also add an *ngIf to check if there is text long enough before showing the button:
<button type="button" *ngIf="p.post_text?.length > 20" (click)="showText(p)" style="margin-top:15px">
{{ p.isReadMore ? 'Read More': 'Read Less' }}
</button>

How can I assign id to rows dynamically in table? [duplicate]

This question already has an answer here:
how to set index variable in primeng p-table like in ngFor
(1 answer)
Closed 1 year ago.
I am using p-table -> https://primefaces.org/primeng/showcase/#/table/dynamic
I want to assign unique id to each row dynamically.
<ng-template pTemplate="body" let-rowData let-columns="columns" let i="index">
<tr id=i>
<td *ngFor="let col of columns">
{{rowData[col.field]}}
</td>
</tr>
</ng-template>
Above code doesnt assign id to the row.
You can try something like this
<ng-template pTemplate="body" let-rowIndex="rowIndex">
<tr>
<td>{{rowIndex}}</td>
</tr>
</ng-template>
if you want to access it in your component you can use such thing
<ng-template pTemplate="body" let-rowData let-columns="columns" let-rowIndex="rowIndex">
<tr [pSelectableRow]="rowData" [pSelectableRowIndex]="rowIndex">
<td *ngFor="let col of columns">
{{rowData[col.field]}}
</td>
</tr>
</ng-template>
btw in order to see more examples, you can navigate to the primeng website:
https://primefaces.org/primeng/showcase/#/table

Vue.js- Render A price comparison on a table

I have a use case, i'm struggling to put together.I want to display prices of goods from different shops like in the picture below.
My problem is i get the following table;
The output on my table is incorrect. Price for Zimgold is only available in Spar. But here its displayed under all other shops. The same goes for Raha.Its Price is supposed to be under OK.I want other shops to have a dash or empty rows if they don't have that product.
Here is my vue.js code:
<modal ref="comparisonTable" no-close-on-backdrop no-close-on-esc hide-cancel width="600px" title="GENERATE BILL" :hide-ok="true" hide-footer centered>
<Spinner v-show="productsLoader" :message="productsLoaderMessage" size="medium"></Spinner>
<table class="table table-striped " v-show="productsTable" width="100%">
<!--Table head-->
<thead>
<tr class="table-info">
<th>Product Brands</th>
<th v-for="company in selectedCompaniesDetails" :key="company.id">
{{company.name}}
</th>
<th>Pick</th>
</tr>
</thead>
<!--Table head-->
<!--Table body-->
<tbody>
<tr v-for="product in products" :key="product.id">
<th scope="row">
{{product.name}}
</th>
<td v-for ="totalCompanies in totalSelectedCompanies" :key="totalCompanies">
<span>
{{product.price}}
</span>
</td>
<td>
<input type="checkbox" style="margin:10px;" v-model="selectedProducts" :value="product"/>
</td>
</tr>
<tr>
<th scope="row">
</th>
<td v-for ="totalCompanies in totalSelectedCompanies" :key="totalCompanies">
</td>
<th>
<button #click="generateFinalBill($event)">
GENERATE BILL
</button>
</th>
</tr>
</tbody>
</table>
</modal>
In your example you have two nested for loops, the outer one loops through products and the inner one loops through totalSelectedCompanies.
The problem is that your product.price variable only changes with each iteration of the outer loop. In order to display a different price for each iteration you would need to have the value be affected by the inner loop.
The way you do this would depend on how the price is stored or calculated but the one way to do this is to store the prices for each seller in the product object and loop through that:
<tr v-for="product in products" :key="product.id">
<th scope="row">
{{product.name}}
</th>
<td v-for ="seller in product.sellers">
<span>
{{seller.price}}
</span>
</td>
<td>
<input type="checkbox" style="margin:10px;" v-model="selectedProducts" :value="product"/>
</td>
</tr>
An issue with this is that you would have to have every seller in product.sellers regardless of if they actually sold it. Instead I would make a method that returns the price of a product from the given company, then you could do something like:
<tr v-for="product in products" :key="product.id">
<th scope="row">
{{product.name}}
</th>
<td v-for ="company in selectedCompanies">
<span>
{{calculateCompanyPrice(product, company)}}
</span>
</td>
<td>
<input type="checkbox" style="margin:10px;" v-model="selectedProducts" :value="product"/>
</td>
</tr>
calculateCompanyPrice() would then contain all the logic for returning the price you need from a given product and company, including displaying a dash if the company does not sell the given product.
Edit: It's worth noting that you can technically put any valid javascript between your {{ }}s, so I'm sure there are many other ways of outputting the proper price within the inner loop.

Angular CDK table: Element 'td' cannot be nested inside element 'table'

I'm trying to use the Angular CDK table in an Angular 7 project in Visual Studio 2017. When I follow the official code sample from here it works perfectly but I get these warnings from visual studio: Element 'td'/'th' cannot be nested inside element 'table'.
Below is the code for reference:
<table cdk-table [dataSource]="masterIds">
<ng-container cdkColumnDef="name">
<th cdk-header-cell *cdkHeaderCellDef> Name </th>
<td cdk-cell *cdkCellDef="let id"> {{ ws[id].Name }} </td>
</ng-container>
<ng-container cdkColumnDef="code">
<th cdk-header-cell *cdkHeaderCellDef> Code </th>
<td cdk-cell *cdkCellDef="let id"> {{ ws[id].Code }} </td>
</ng-container>
<tr class="small" cdk-header-row *cdkHeaderRowDef="['name', 'code']"></tr>
<tr cdk-row *cdkRowDef="let id; columns: ['name', 'code']" [routerLink]="['./', id]"></tr>
</table>
Any idea how to get rid of these warnings? They are poking me in the eye. Thanks
UPDATE
Here is a picture of the warnings:
And the green squiggles :
I just found a way which "seems" to work; wrap all the column definitions inside an invisible <tr> element and that's that:
<table cdk-table [dataSource]="masterIds">
<tr style="display:none!important">
<ng-container cdkColumnDef="name">
<th cdk-header-cell *cdkHeaderCellDef> Name </th>
<td cdk-cell *cdkCellDef="let id"> {{ ws[id].Name }} </td>
</ng-container>
<ng-container cdkColumnDef="code">
<th cdk-header-cell *cdkHeaderCellDef> Code </th>
<td cdk-cell *cdkCellDef="let id"> {{ ws[id].Code }} </td>
</ng-container>
</tr>
<tr class="small" cdk-header-row *cdkHeaderRowDef="['name', 'code']"></tr>
<tr cdk-row *cdkRowDef="let id; columns: ['name', 'code']" [routerLink]="['./', id]"></tr>
</table>
A th should be inside a tr, you should have something like :
<table>
<tr>
<th></th>
<th></th>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</table>
For your updated question
That's because of the Visual studio compiler itself, you can search for How to suppress VS studio compiler warnings (try this), and a tip: switch to VsCode for your frontend dev.

Create Angular2 Table Header with *ngFor loop

So, my problem is so basic but i cant solve it.
I'm trying to create dynamic table header with *ngFor.
<table>
<tr>
<th>Entry Warehouse</th>
<th colspan="2" *ngFor="let data of datas">
SomeText
</th>
</tr>
<tr>
<th>More Text</th>
<div *ngFor="let data of datas">
<th>A little text again</th>
<th>A little text again</th>
</div>
</tr>
</table>
Anyway, this solution suicide themself at that point. If datas length more than 1, div tag is underscoring th tag in same cell.
If i try another solution like this;
<table>
<tr>
<th>Entry Warehouse</th>
<th colspan="2" *ngFor="let data of datas">
SomeText
</th>
</tr>
<tr>
<th>More Text</th>
<th *ngFor="let data of datas">A little text again</th>
<th *ngFor="let data of datas">A little text again</th>
</tr>
</table>
it looks like works but actually not. Because at this time the next th tag doesnt start before the previous loop ends.
In angular 2+ you can use<ng-container> tags
<ng-conatiner *ngFor="let i of items">
<th>i</th>
</ng-conatiner>