-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Add extensible table row detail feature #24636
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
2a7ff01
677cc0a
abb68b3
41e3ef8
5c00d36
c81124d
66cdfa2
6e63bb0
f28dfe1
2bcafa7
94c0b60
3113cbe
6391840
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,189 @@ | ||||
| ```json | ||||
| //[doc-seo] | ||||
| { | ||||
| "Description": "Learn how to add expandable row details to data tables using the Extensible Table Row Detail component in ABP Framework Angular UI." | ||||
| } | ||||
| ``` | ||||
|
|
||||
| # Extensible Table Row Detail for Angular UI | ||||
|
|
||||
| ## Introduction | ||||
|
|
||||
| The `<abp-extensible-table-row-detail>` component allows you to add expandable row details to any `<abp-extensible-table>`. When users click the expand icon, additional content is revealed below the row. | ||||
|
|
||||
| <img alt="Extensible Table Row Detail Example" src="./images/extensible-table-row-detail-example.png" width="800px" style="max-width:100%"> | ||||
|
|
||||
|
||||
| <img alt="Extensible Table Row Detail Example" src="./images/extensible-table-row-detail-example.png" width="800px" style="max-width:100%"> |
fahrigedik marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { Component, contentChild, input, TemplateRef } from '@angular/core'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we still support the module usage in the packages, it is better to import this component |
||
|
|
||
| @Component({ | ||
| selector: 'abp-extensible-table-row-detail', | ||
| template: '', | ||
| }) | ||
| export class ExtensibleTableRowDetailComponent<R = any> { | ||
| readonly rowHeight = input<string | number>('100%'); | ||
| readonly template = contentChild(TemplateRef<{ row: R; expanded: boolean }>); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './extensible-table-row-detail.component'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,30 @@ | ||
| @if (isBrowser) { | ||
| <ngx-datatable | ||
| default | ||
| [rows]="data" | ||
| [count]="recordsTotal" | ||
| [list]="list" | ||
| [selectionType]="selectable ? _selectionType : undefined" | ||
| (activate)="tableActivate.emit($event)" | ||
| (select)="onSelect($event)" | ||
| [selected]="selected" | ||
| (scroll)="onScroll($event)" | ||
| [scrollbarV]="infiniteScroll" | ||
| [style.height]="getTableHeight()" | ||
| [loadingIndicator]="infiniteScroll && isLoading" | ||
| [footerHeight]="infiniteScroll ? false : 50" | ||
| > | ||
| @if(selectable) { | ||
| <ngx-datatable-column [width]="50" [sortable]="false" [canAutoResize]="false" [draggable]="false" [resizeable]="false"> | ||
| <ngx-datatable #table default [rows]="data" [count]="recordsTotal" [list]="list" | ||
| [selectionType]="selectable ? _selectionType : undefined" (activate)="tableActivate.emit($event)" | ||
| (select)="onSelect($event)" [selected]="selected" (scroll)="onScroll($event)" [scrollbarV]="infiniteScroll" | ||
| [style.height]="getTableHeight()" [loadingIndicator]="infiniteScroll && isLoading" | ||
| [footerHeight]="infiniteScroll ? false : 50"> | ||
| @if (effectiveRowDetailTemplate) { | ||
| <ngx-datatable-row-detail [rowHeight]="effectiveRowDetailHeight"> | ||
| <ng-template let-row="row" let-expanded="expanded" class="bg-transparent" ngx-datatable-row-detail-template> | ||
| <ng-container class="bg-transparent" | ||
fahrigedik marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| *ngTemplateOutlet="effectiveRowDetailTemplate; context: { row: row, expanded: expanded }" /> | ||
| </ng-template> | ||
| </ngx-datatable-row-detail> | ||
|
|
||
| <ngx-datatable-column [width]="50" [resizeable]="false" [sortable]="false" [draggable]="false" | ||
| [canAutoResize]="false"> | ||
| <ng-template let-row="row" let-expanded="expanded" ngx-datatable-cell-template> | ||
| <a href="javascript:void(0)" class="text-decoration-none text-muted" | ||
|
||
| [attr.aria-label]="expanded ? 'Collapse' : 'Expand'" (click)="toggleExpandRow(row)"> | ||
| <i class="fa" [class.fa-chevron-down]="!expanded" [class.fa-chevron-up]="expanded"></i> | ||
| </a> | ||
| </ng-template> | ||
| </ngx-datatable-column> | ||
| } | ||
| @if(selectable) { | ||
| <ngx-datatable-column [width]="50" [sortable]="false" [canAutoResize]="false" [draggable]="false" | ||
| [resizeable]="false"> | ||
|
|
||
| <ng-template ngx-datatable-header-template let-value="value" let-allRowsSelected="allRowsSelected" | ||
| let-selectFn="selectFn"> | ||
|
|
@@ -44,97 +53,67 @@ | |
| </ngx-datatable-column> | ||
| } | ||
| @if (actionsTemplate || (actionList.length && hasAtLeastOnePermittedAction)) { | ||
| <ngx-datatable-column | ||
| [name]="actionsText | abpLocalization" | ||
| [maxWidth]="_actionsColumnWidth() ?? undefined" | ||
| [width]="_actionsColumnWidth() ?? 200" | ||
| [canAutoResize]="!_actionsColumnWidth()" | ||
| [sortable]="false" | ||
| > | ||
| <ng-template let-row="row" let-i="rowIndex" ngx-datatable-cell-template> | ||
| <ng-container | ||
| *ngTemplateOutlet="actionsTemplate || gridActions; context: { $implicit: row, index: i }" | ||
| ></ng-container> | ||
| <ng-template #gridActions> | ||
| @if (isVisibleActions(row)) { | ||
| <abp-grid-actions [index]="i" [record]="row" text="AbpUi::Actions"></abp-grid-actions> | ||
| } | ||
| </ng-template> | ||
| <ngx-datatable-column [name]="actionsText | abpLocalization" [maxWidth]="_actionsColumnWidth() ?? undefined" | ||
| [width]="_actionsColumnWidth() ?? 200" [canAutoResize]="!_actionsColumnWidth()" [sortable]="false"> | ||
| <ng-template let-row="row" let-i="rowIndex" ngx-datatable-cell-template> | ||
| <ng-container | ||
| *ngTemplateOutlet="actionsTemplate || gridActions; context: { $implicit: row, index: i }"></ng-container> | ||
| <ng-template #gridActions> | ||
| @if (isVisibleActions(row)) { | ||
| <abp-grid-actions [index]="i" [record]="row" text="AbpUi::Actions"></abp-grid-actions> | ||
| } | ||
| </ng-template> | ||
| </ngx-datatable-column> | ||
| </ng-template> | ||
| </ngx-datatable-column> | ||
| } | ||
| @for (prop of propList; track prop.name; let i = $index) { | ||
| <ngx-datatable-column | ||
| *abpVisible="prop.columnVisible(getInjected)" | ||
| [width]="columnWidths[i] ?? 200" | ||
| [canAutoResize]="!columnWidths[i]" | ||
| [name]="(prop.isExtra ? '::' + prop.displayName : prop.displayName) | abpLocalization" | ||
| [prop]="prop.name" | ||
| [sortable]="prop.sortable" | ||
| > | ||
| <ng-template ngx-datatable-header-template let-column="column" let-sortFn="sortFn"> | ||
| @if (prop.tooltip) { | ||
| <span | ||
| [ngbTooltip]="prop.tooltip.text | abpLocalization" | ||
| [placement]="prop.tooltip.placement || 'auto'" | ||
| container="body" | ||
| [class.pointer]="prop.sortable" | ||
| (click)="prop.sortable && sortFn(column)" | ||
| > | ||
| {{ column.name }} <i class="fa fa-info-circle" aria-hidden="true"></i> | ||
| </span> | ||
| } @else { | ||
| <span | ||
| [class.pointer]="prop.sortable" | ||
| (click)="prop.sortable && sortFn(column)" | ||
| > | ||
| {{ column.name }} | ||
| </span> | ||
| } | ||
| </ng-template> | ||
| <ng-template let-row="row" let-i="index" ngx-datatable-cell-template> | ||
| <ng-container *abpPermission="prop.permission; runChangeDetection: false"> | ||
| <ng-container *abpVisible="row['_' + prop.name]?.visible"> | ||
| @if (!row['_' + prop.name].component) { | ||
| @if (prop.type === 'datetime' || prop.type === 'date' || prop.type === 'time') { | ||
| <div | ||
| [innerHTML]=" | ||
| <ngx-datatable-column *abpVisible="prop.columnVisible(getInjected)" [width]="columnWidths[i] ?? 200" | ||
| [canAutoResize]="!columnWidths[i]" | ||
| [name]="(prop.isExtra ? '::' + prop.displayName : prop.displayName) | abpLocalization" [prop]="prop.name" | ||
| [sortable]="prop.sortable"> | ||
| <ng-template ngx-datatable-header-template let-column="column" let-sortFn="sortFn"> | ||
| @if (prop.tooltip) { | ||
| <span [ngbTooltip]="prop.tooltip.text | abpLocalization" [placement]="prop.tooltip.placement || 'auto'" | ||
| container="body" [class.pointer]="prop.sortable" (click)="prop.sortable && sortFn(column)"> | ||
| {{ column.name }} <i class="fa fa-info-circle" aria-hidden="true"></i> | ||
| </span> | ||
| } @else { | ||
| <span [class.pointer]="prop.sortable" (click)="prop.sortable && sortFn(column)"> | ||
| {{ column.name }} | ||
| </span> | ||
| } | ||
| </ng-template> | ||
| <ng-template let-row="row" let-i="index" ngx-datatable-cell-template> | ||
| <ng-container *abpPermission="prop.permission; runChangeDetection: false"> | ||
| <ng-container *abpVisible="row['_' + prop.name]?.visible"> | ||
| @if (!row['_' + prop.name].component) { | ||
| @if (prop.type === 'datetime' || prop.type === 'date' || prop.type === 'time') { | ||
| <div [innerHTML]=" | ||
| !prop.isExtra | ||
| ? (row['_' + prop.name]?.value | async | abpUtcToLocal:prop.type) | ||
| : ('::' + (row['_' + prop.name]?.value | async | abpUtcToLocal:prop.type) | abpLocalization) | ||
| " | ||
| (click)=" | ||
| " (click)=" | ||
| prop.action && prop.action({ getInjected: getInjected, record: row, index: i }) | ||
| " | ||
| [ngClass]="entityPropTypeClasses[prop.type]" | ||
| [class.pointer]="prop.action" | ||
| ></div> | ||
| } @else { | ||
| <div | ||
| [innerHTML]=" | ||
| " [ngClass]="entityPropTypeClasses[prop.type]" [class.pointer]="prop.action"></div> | ||
| } @else { | ||
| <div [innerHTML]=" | ||
| !prop.isExtra | ||
| ? (row['_' + prop.name]?.value | async) | ||
| : ('::' + (row['_' + prop.name]?.value | async) | abpLocalization) | ||
| " | ||
| (click)=" | ||
| " (click)=" | ||
| prop.action && prop.action({ getInjected: getInjected, record: row, index: i }) | ||
| " | ||
| [ngClass]="entityPropTypeClasses[prop.type]" | ||
| [class.pointer]="prop.action" | ||
| ></div> | ||
| } | ||
| } @else { | ||
| <ng-container | ||
| *ngComponentOutlet=" | ||
| " [ngClass]="entityPropTypeClasses[prop.type]" [class.pointer]="prop.action"></div> | ||
| } | ||
| } @else { | ||
| <ng-container *ngComponentOutlet=" | ||
| row['_' + prop.name].component; | ||
| injector: row['_' + prop.name].injector | ||
| " | ||
| ></ng-container> | ||
| } | ||
| </ng-container> | ||
| "></ng-container> | ||
| } | ||
| </ng-container> | ||
| </ng-template> | ||
| </ngx-datatable-column> | ||
| </ng-container> | ||
| </ng-template> | ||
| </ngx-datatable-column> | ||
| } | ||
| </ngx-datatable> | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This document is not referenced in another one, so we can make a reference here to make it visible.