projects/congarevenuecloud/elements/src/lib/data/table/table.component.ts
Table component allows users to show list of AObject records in a grid view. User can pass table options with list of fields to be displayed as table columns. The table also provides more advanced features like data search, pagination, sortable columns, row selections with mass actions etc.
import { TableModule } from '@congarevenuecloud/elements';
@NgModule({
imports: [TableModule, ...]
})
export class AppModule {}
// Basic usage.
```typescript
<apt-table
[type]="aObjectType"
[options]="TableOptions"></apt-table>
OnChanges
OnDestroy
changeDetection | ChangeDetectionStrategy.OnPush |
encapsulation | ViewEncapsulation.None |
selector | apt-table |
styleUrls | ./table.component.scss |
templateUrl | ./table.component.html |
Properties |
Methods |
Inputs |
constructor(metadataService: MetadataService, cdr: ChangeDetectorRef, apiService: ApiService, sanitizer: DomSanitizer, translate: TranslateService, accountService: AccountService)
|
|||||||||||||||||||||
Parameters :
|
hideSelectAll |
Type : boolean
|
Default value : false
|
Flag to hide select all checkbox. |
options |
Type : TableOptions
|
Configuration options for the table component of type TableOptions |
showErrorToaster |
Type : boolean
|
Default value : false
|
Flag to enable error toaster messages. |
showMassActionConfirmationAlert |
Type : boolean
|
Default value : false
|
Flag to enable confirmation alert messages for mass action. |
showTableControls |
Type : boolean
|
Default value : true
|
Flag to show table controls on the table component such as pagination, search etc. |
type |
Type : ClassType<AObject>
|
Reference of AObject class type. |
executeAction | ||||||||||||
executeAction(action: TableAction, record: AObject | Array
|
||||||||||||
Executes row action or mass action on selected table record(s).
Parameters :
Returns :
void
|
openModal | ||||||||||||
openModal(product: Product, relatedTo: CartItem | AssetLineItem)
|
||||||||||||
Opens the product configuration summary modal with the given product.
Parameters :
Returns :
void
|
sort | ||||||||
sort(column: string)
|
||||||||
This methods sorts the table column.
Parameters :
Returns :
void
|
updateSelectedRecordList | ||||||||||||
updateSelectedRecordList(state: CheckState, record: AObject)
|
||||||||||||
Updates the selected records array with the currently selected records in the table.
Parameters :
Returns :
void
|
accountId |
Type : string
|
Default value : ''
|
configProduct |
Type : Product
|
Use to hold configuration data for a data in the table. |
disableGrouping |
Type : boolean
|
Default value : false
|
Flag to check if data can grouped in the table . |
expanded |
Type : boolean
|
Default value : true
|
Flag to determines Whether the AccordionItem is expanded for table data. |
maxCharacterLength |
Type : number
|
Default value : 20
|
Use to to show popover after given number of character for data in the table. |
page |
Type : number
|
Default value : 1
|
Use for pagination. |
relatedTo |
Type : CartItem | AssetLineItem
|
Use to hold configuration data for a data in the table. |
searchedStringValue |
Type : string
|
Default value : ''
|
To hold previous searched string value while switiching window |
searchString |
Type : string
|
Use for search data in table. |
selectedRecords |
Type : Array<AObject>
|
Default value : []
|
Array of selected data from table. |
sortColumn |
Type : string
|
Use for sort the columns in table. |
sortDirection |
Type : "ASC" | "DESC"
|
Use for sorting direction for the columns in table. |
<ng-container *ngTemplateOutlet="dataTable; context: {view: view$ | async}"></ng-container>
<ng-template #dataTable let-view="view">
<div>
<div *ngIf="showTableControls" class="table-controls">
<div class="row py-2 no-gutters align-items-center justify-content-between"
*ngIf="!options?.groupBy; else forGroup">
<form
class="col-sm-12 col-md-2 col-lg-12 d-flex justify-content-end input-group input-group-sm col-4 inline"
(ngSubmit)="loadData()">
<div class="input-icons mb-0">
<i class="fas fa-search icon"></i>
<input type="text" class="form-control input-field" [placeholder]="'TABLE.SEARCH' | translate"
[(ngModel)]="searchString" name="searchString" (keyup)="loadDataDebounce($event)">
</div>
</form>
<div class="col-sm-12 col-md-10 col-lg-7 col-12 d-flex justify-content-end" *ngIf="view?.pageStats">
<div class="input-group input-group-sm px-lg-5 px-md-3 px-sm-1 px-1">
<div>
<label class="pr-2" for="sort">Rows per page</label>
</div>
<select class="custom-select custom-select-sm pagination-select" id="size"
[(ngModel)]="view.limit" name="plan" (change)="onLimitChange()">
<option [value]="option" *ngFor="let option of view?.limitOptions">{{option}}</option>
</select>
</div>
<div class="d-flex pt-1">
<div class="px-2 font-weight-bold">
{{'PRODUCT_LIST.SHOW_COUNT_OF_RECORDS_MESSAGE' | translate:view?.pageStats}}
</div>
<div class="px-1 d-flex">
<pagination [totalItems]="view?.totalRecords" [(ngModel)]="page" [maxSize]="1"
[itemsPerPage]="view.limit" pageBtnClass="btn btn-link"
(pageChanged)="onPageChange($event)" nextText=" " previousText=" ">
</pagination>
<span class="class font-weight-bold">{{'TABLE.OF' | translate}} {{ view?.totalPages}}
{{'COMMON.PAGES' |
translate}}</span>
</div>
</div>
</div>
</div>
<ng-template #forGroup>
<div class="row py-2 no-gutters align-items-center justify-content-between">
<form
class="col-sm-12 col-md-2 col-lg-4 d-flex justify-content-end input-group input-group-sm col-4 inline"
(ngSubmit)="loadData()">
<div class="input-icons mb-0">
<i class="fas fa-search icon"></i>
<input type="text" class="form-control input-field"
[placeholder]="'TABLE.SEARCH' | translate" [(ngModel)]="searchString"
name="searchString" (keyup)="loadDataDebounce($event)">
</div>
</form>
<div class="col-sm-12 col-md-10 col-lg-8 col-12 d-flex justify-content-end" *ngIf="view?.pageStats">
<div class="input-group input-group-sm px-lg-3 px-md-3 px-sm-0 px-0">
<div>
<label class="pr-2" for="sort">{{'TABLE.ROWS_PER_PAGE' | translate}}</label>
</div>
<select class="custom-select custom-select-sm pagination-select" id="size"
[(ngModel)]="view.limit" name="plan" (change)="onLimitChange()">
<option [value]="option" *ngFor="let option of view?.limitOptions">{{option}}</option>
</select>
</div>
<div class="d-flex pr-lg-3 pr-md-3 pr-sm-0 pr-0">
<div class="px-2 font-weight-bold">
{{'PRODUCT_LIST.SHOW_COUNT_OF_RECORDS_MESSAGE' | translate:view?.pageStats}}
</div>
<div class="px-1 d-flex">
<pagination [totalItems]="view?.totalRecords" [(ngModel)]="page" [maxSize]="1"
[itemsPerPage]="view.limit" pageBtnClass="btn btn-link"
(pageChanged)="onPageChange($event)" nextText=" " previousText=" ">
</pagination>
<span class="class font-weight-bold">{{'TABLE.OF' | translate}} {{ view?.totalPages}}
{{'COMMON.PAGES' |
translate}}</span>
</div>
</div>
<div *ngIf="options?.groupBy"
class="border-left d-lg-flex d-md-flex d-sm-none d-none align-items-center">
<button class="btn btn-link pl-3 pr-2" [class.disabled]="disableGrouping !== true"
[disabled]="disableGrouping !== true" (click)="disableGrouping = false">
<i class="fa fa-layer-group"></i>
</button>
<button class="btn btn-link px-2" [class.disabled]="disableGrouping === true"
[disabled]="disableGrouping === true" (click)="disableGrouping = true">
<i class="fa fa-list"></i>
</button>
</div>
</div>
</div>
</ng-template>
<div class="d-flex" *ngIf="options?.actions?.length">
<div class="border-right pr-3">
{{view?.selectedItemCount}} {{'TABLE.SELECTED' | translate}}
</div>
<div class="d-flex">
<button class="btn btn-link" [ngClass]="'text-' + action?.theme"
*ngFor="let action of view?.actions" [disabled]="!action.enabled"
(click)="executeAction(action, selectedRecords)">
<i class="fa" [ngClass]="action?.icon" [title]="action?.label"></i>
</button>
</div>
</div>
</div>
<div class="d-flex align-items-stretch border-top" *ngIf="view?.data?.length > 0">
<div>
<ng-container
*ngTemplateOutlet="table; context: {view: view, columns: view?.stickyColumns, actions: options?.actions?.length > 0, stickySection: true}">
</ng-container>
</div>
<div class="table-responsive">
<ng-scrollbar [track]="'horizontal'">
<ng-container
*ngTemplateOutlet="table; context: {view: view, columns: view?.columns, actions: false}">
</ng-container>
</ng-scrollbar>
</div>
</div>
<ng-container #empty *ngIf="view?.data?.length === 0">
<div class="d-flex justify-content-center align-items-center m-5 p-5 flex-column">
<i class="fa fa-database fa-5x text-primary xl text-faded"></i>
<div class="mt-4">{{'TABLE.NO_DATA_TO_DISPLAY' | translate}}</div>
</div>
</ng-container>
<ng-container #dataLoading *ngIf="!view?.data">
<div class="d-flex justify-content-center align-items-center m-5 p-5 flex-column">
<apt-dots></apt-dots>
</div>
</ng-container>
<ng-template #table let-actions="actions" let-view="view" let-columns="columns"
let-stickySection="stickySection">
<table class="table mb-0 mt-0">
<thead>
<tr>
<!-- Checkbox header -->
<th scope="col" class="action pb-0 pt-0" *ngIf="actions && !hideSelectAll">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="mainCheckbox"
[indeterminate]="view?.checkState === 'indeterminate'"
[checked]="view?.checkState === 'checked'" (click)="toggleAll($event)">
<label class="custom-control-label" for="mainCheckbox"> </label>
</div>
</th>
<th *ngIf="actions && !(options?.groupBy)" aria-hidden="true"></th>
<!-- Column headers -->
<th scope="col" *ngFor="let column of columns; let f = first" [attr.colspan]="actions ? 2 : 1"
[class.py-0]="(options?.groupBy && stickySection)">
<div class="pr-4 d-flex text-truncate">
<button class="btn chevron p-0" *ngIf="options?.groupBy && f && actions"
[attr.aria-expanded]="expanded" (click)="expanded = !expanded"></button>
<button class="btn p-0 text-truncate"
*ngIf="column?.sortable !== false; else nonSortableColumn"
(click)="sort(column.prop)">
<apt-output-field [record]="view?.type" [field]="column.prop" layout="inline"
[labelOnly]="true" [label]="column.label">
<i class="fa fa-long-arrow-down space" *ngIf="column.prop === sortColumn"
data-toggle="tooltip" data-placement="top" tooltip="{{sortDirection}}"
[class.fa-long-arrow-down]="sortDirection === 'DESC'"
[class.fa-long-arrow-up]="sortDirection === 'ASC'"></i>
</apt-output-field>
</button>
<ng-template #nonSortableColumn>
<button class="btn p-0 text-truncate pe-none">
<apt-output-field [record]="view?.type" [field]="column.prop" layout="inline"
[labelOnly]="true" [label]="column.label">
<i class="fa fa-long-arrow-down space" *ngIf="column.prop === sortColumn"
data-toggle="tooltip" data-placement="top"
tooltip="sortDirection === 'DESC' ? 'Sort Descending' : 'Sort Ascending'"
[class.fa-long-arrow-down]="sortDirection === 'DESC'"
[class.fa-long-arrow-up]="sortDirection === 'ASC'"></i>
</apt-output-field>
</button>
</ng-template>
</div>
</th>
</tr>
</thead>
<tbody>
<ng-container *ngIf="view?.groups?.length > 0 && !disableGrouping; else flat">
<ng-container *ngFor="let group of view?.groups; let i = index">
<tr class="table-secondary">
<!-- Checkbox column -->
<td class="action pb-1 pt-1 px-0" *ngIf="actions">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" [id]="'check-'+ group.label"
[indeterminate]="group.state === 'indeterminate'"
[checked]="group.state === 'checked'" (click)="toggleGroup(group)">
<label class="custom-control-label" [for]="'check-'+ group.label"> </label>
</div>
</td>
<!-- Data columns -->
<td [attr.colspan]="actions ? columns?.length + 1 : columns?.length"
class="text-truncate pb-1 pt-1 px-2">
<button class="btn btn-sm chevron px-1" type="button" data-toggle="collapse"
[attr.data-target]="'#child-' + i" [attr.aria-expanded]="expanded"
*ngIf="options?.groupBy && stickySection">
<ng-container *ngIf="group?.label?.length > maxCharacterLength; else simple">
<span [popover]="group?.label" triggers="mouseover" container="body">
{{group?.label.substring(0, maxCharacterLength - 3) + '...'}}
</span>
</ng-container>
<ng-template #simple>
{{group?.label}}
</ng-template>
</button>
</td>
</tr>
<ng-container
*ngTemplateOutlet="rowData; context: {data: view?.groupedData[group.label], group: group, groupIndex: i, actions: actions, columns: columns, route: view?.route}">
</ng-container>
</ng-container>
</ng-container>
<ng-template #flat>
<ng-container
*ngTemplateOutlet="rowData; context: {data: view?.data, actions: actions, columns: columns, route: view?.route}">
</ng-container>
</ng-template>
<ng-template #rowData let-data="data" let-group="group" let-groupIndex="groupIndex"
let-actions="actions" let-columns="columns" let-route="route">
<tr *ngFor="let record of data; trackBy: trackById" class="collapse childSection"
[class.show]="expanded" [id]="'child-' + groupIndex"
[ngClass]="(canHighlightRow(record) | async) ? 'table-success' : ''"
[class.table-primary]="record?._metadata?.state === 'indeterminate' || record?._metadata?.state === 'checked'">
<!-- Checkbox column -->
<td class="action pb-1 pt-1 px-0" *ngIf="actions">
<div class="custom-control custom-checkbox" *ngIf="showCheckbox(record)">
<input type="checkbox" class="custom-control-input" [id]="'check-' + record?.Id"
[checked]="record?.get('state') === 'checked'"
(click)="toggleRecord(record, group)">
<label class="custom-control-label" [for]="'check-' + record?.Id"> </label>
</div>
</td>
<!-- Action columns -->
<td class="action pb-1 pt-1 px-0" *ngIf="actions">
<div class="btn-group m-0" *ngIf="record?.get('actions')?.length > 0" dropdown
[dropup]="dropUp[i]">
<button class="btn btn-link" dropdownToggle
(click)="toggleTableActionDropdown($event, i)">
<div *ngIf="record?.get('state') === 'processing'">
<i class="fas fa-spinner fa-spin d-flex align-self-center"></i>
</div>
<div *ngIf="record?.get('state') !== 'processing'">
<i class="fa fa-ellipsis-v d-flex align-self-center fa-sm"></i>
</div>
</button>
<div *dropdownMenu class="dropdown-menu border" [class.dropup]="dropUp[i]">
<h6 class="dropdown-header font-weight-bold">{{record?.Name}}</h6>
<div class="dropdown-divider m-0"></div>
<a href="javascript:void(0)" class="dropdown-item flex-nowrap"
*ngFor="let action of record?.get('actions')"
[ngClass]="'text-' + action?.theme" (click)="executeAction(action, record)">
<i class="fa mr-2" [ngClass]="action?.icon"></i>
{{action?.label | translate}}
</a>
</div>
</div>
</td>
<td *ngFor="let column of columns; let firstColumn = first" class="text-truncate"
[attr.colspan]="actions ? 2 : 1">
<!-- If custom values provided, render that. else show backend data. -->
<div *ngIf="column.value; else renderRecord"> {{column.value(record) | async}}
</div>
<!-- No Custom data provided. -->
<ng-template #renderRecord>
<div class="d-flex align-items-center">
<a href="javascript:void(0)" [routerLink]="[route, record?.Id]"
*ngIf="(column.prop === 'Name' && !options.disableLink); else read">
<apt-output-field [record]="record" [field]="column.prop" valueOnly="true"
[editable]="false" [maxCharacterLength]="maxCharacterLength">
</apt-output-field>
</a>
<ng-template #read>
<div>
<apt-output-field [record]="record" [field]="column.prop"
[displayValue]="column.prop" valueOnly="true" [editable]="false"
[maxCharacterLength]="column.showPopover ? maxCharacterLength : null "
[showQuickView]="column.showPopover ? false : true">
</apt-output-field>
</div>
</ng-template>
<i *ngIf="firstColumn && stickySection && view?.childRecords[record?.Id]?.length > 0"
class="fas fa-info-circle ml-2 text-primary" [popover]="popTemplate"
containerClass="childPopover p-2" [outsideClick]="true"></i>
<button class="text-muted btn btn-link btn-sm p-0 ml-2"
(click)="openModal(record?.Product, record)"
*ngIf="firstColumn && stickySection && (record?.HasAttributes || record?.HasOptions) && record?.BusinessLineItemId">
<i class="fas fa-wrench"></i>
</button>
<ng-template #popTemplate>
<div>
<table class="table mb-0">
<thead>
<tr>
<th *ngFor="let column of options?.childRecordOptions?.childRecordFields"
scope="col">{{column}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let childRecord of view?.childRecords[record?.Id]">
<td
*ngFor="let column of options?.childRecordOptions?.childRecordFields">
<apt-output-field [record]="childRecord"
[field]="column" valueOnly="true"
[editable]="false"></apt-output-field>
</td>
</tr>
</tbody>
</table>
</div>
</ng-template>
</div>
</ng-template>
</td>
</tr>
</ng-template>
</tbody>
</table>
</ng-template>
</div>
<apt-product-configuration-summary *ngIf="configProduct" #productConfigurationSummary [product]="configProduct.Id"
[relatedTo]="relatedTo">
</apt-product-configuration-summary>
</ng-template>
./table.component.scss
apt-table {
.collapsing {
-webkit-transition: none;
transition: none;
display: none;
}
.table-controls {
div.d-flex {
align-items: center;
}
.input-group {
width: auto;
align-items: center;
}
pagination {
ul {
margin-bottom: 0;
li:first-child,
li:last-child {
a {
&.page-link {
padding: 0.5em !important;
}
}
}
li {
display: flex;
align-items: center;
&.pagination-next>a:before {
font-family: "Font Awesome 5 Free";
font-weight: 400;
content: "\f054";
}
&.pagination-prev>a:before {
font-family: "Font Awesome 5 Free";
font-weight: 400;
content: "\f053";
}
&.page-item.active .page-link {
background-color: inherit;
color: inherit;
box-sizing: border-box;
height: 2.25rem;
top: 0px;
background: transparent;
border: 1px solid rgba(168, 178, 187, 1);
border-radius: 0.25rem;
margin-right: 0.2rem;
}
&.page-item.disabled .page-link {
background: transparent;
}
a {
&.page-link {
border: none;
}
}
}
}
}
}
table.table {
td,
th {
border-bottom: 1px solid #c6cdd2;
vertical-align: middle;
height: 39px;
}
.action {
width: 1rem;
vertical-align: middle;
text-align: center;
}
.loading-indicator {
height: 10px;
border-radius: 5px;
}
}
.childPopover {
max-width: unset;
}
apt-output-field dd {
padding:0;
}
.fa-trash {
color: #657888 !important;
}
div.px-2 {
font-size: 0.75rem;
line-height: 1.125rem;
color: #4c5f6f;
}
span.class {
font-size: 0.75rem;
line-height: 1.125rem;
color: #4c5f6f;
}
.form-control {
background: #ffffff;
border: 1px solid #a8b2bb;
border-radius: 0.25rem !important;
}
.inline {
max-width: 15.625rem;
}
.input-icons i {
position: absolute;
}
.input-icons {
width: 100%;
margin-bottom: 0.625rem;
}
.icon {
padding: 0.625rem;
min-width: 2.5rem;
color: #657888;
}
.input-field {
width: 100%;
padding-left: 2.1875rem !important;
margin-bottom: 0rem;
}
form.col-12 {
margin-bottom: 0rem;
padding-right: 1.875rem;
}
.dropdown-menu {
transform: translateY(0%) !important;
&.dropup {
transform: translateY(-101%) !important;
}
}
div.pr-4 {
button.text-truncate {
apt-output-field {
dt.class {
color: black !important;
font-weight: 600 !important;
font-size: 0.75rem !important;
line-height: 1.125rem !important;
}
}
}
}
}
.pagination-select {
box-sizing: border-box !important;
width: 3.25rem !important;
height: 2.25rem !important;
top: 0px !important;
background: transparent !important;
border: 1px solid rgba(168, 178, 187, 1) !important;
border-radius: 0.25rem !important;
padding-right: 0 !important;
appearance: auto;
}
// popover-container.show{
// div.popover-arrow{
// display: none !important;
// }
// div.popover-body {
// background-color: darkgrey !important;
// color: white;
// border-radius: 4px;
// }
// }