File

projects/congarevenuecloud/elements/src/lib/data/table/table.component.ts

Description

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.

Preview

Usage

Example :
import { TableModule } from '@congarevenuecloud/elements';
@NgModule({
imports: [TableModule, ...]
})
export class AppModule {}
Example :
// Basic usage.
```typescript
<apt-table
            [type]="aObjectType"
            [options]="TableOptions"></apt-table>
Example :

Implements

OnChanges OnDestroy

Metadata

Index

Properties
Methods
Inputs

Constructor

constructor(metadataService: MetadataService, cdr: ChangeDetectorRef, apiService: ApiService, sanitizer: DomSanitizer, translate: TranslateService, accountService: AccountService)
Parameters :
Name Type Optional
metadataService MetadataService No
cdr ChangeDetectorRef No
apiService ApiService No
sanitizer DomSanitizer No
translate TranslateService No
accountService AccountService No

Inputs

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.

Methods

executeAction
executeAction(action: TableAction, record: AObject | Array)

Executes row action or mass action on selected table record(s).

Parameters :
Name Type Optional Description
action TableAction No

table action on the record(s) to be executed.

record AObject | Array<AObject> No

list of selected table records.

Returns : void
openModal
openModal(product: Product, relatedTo: CartItem | AssetLineItem)

Opens the product configuration summary modal with the given product.

Parameters :
Name Type Optional Description
product Product No

The product referenced by the configuration modal.

relatedTo CartItem | AssetLineItem No

Cart item or asset item.

Returns : void
sort
sort(column: string)

This methods sorts the table column.

Parameters :
Name Type Optional Description
column string No

referenced by the table.

Returns : void
updateSelectedRecordList
updateSelectedRecordList(state: CheckState, record: AObject)

Updates the selected records array with the currently selected records in the table.

Parameters :
Name Type Optional Description
state CheckState No

The check state of the record being updated.

record AObject No

The record being updated.

Returns : void

Properties

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="&nbsp;" previousText="&nbsp;">
                            </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="&nbsp;" previousText="&nbsp;">
                                </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">&nbsp;</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">&nbsp;</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">&nbsp;</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;
//   }
// }
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""