src/app/shared/datatable/table/table.component.ts
AfterContentChecked
OnInit
OnChanges
OnDestroy
selector | cd-table |
styleUrls | ./table.component.scss |
templateUrl | ./table.component.html |
constructor(ngZone: NgZone, cdRef: ChangeDetectorRef)
|
|||||||||
Parameters :
|
autoReload | |
Type : any
|
|
Default value : 5000
|
|
Auto reload time in ms - per default every 5s You can set it to 0, undefined or false to disable the auto reload feature in order to trigger 'fetchData' if the reload button is clicked. |
autoSave | |
Default value : true
|
|
columnMode | |
Default value : 'flex'
|
|
columns | |
Type : CdTableColumn[]
|
|
customCss | |
Type : literal type
|
|
data | |
Type : any[]
|
|
footer | |
Default value : true
|
|
forceIdentifier | |
Default value : false
|
|
header | |
Default value : true
|
|
identifier | |
Default value : 'id'
|
|
limit | |
Default value : 10
|
|
selectionType | |
Type : string
|
|
Default value : undefined
|
|
sorts | |
Type : SortPropDir[]
|
|
toolHeader | |
Default value : true
|
|
updateSelectionOnRefresh | |
Type : "always" | "never" | "onChange"
|
|
Default value : 'onChange'
|
|
fetchData | |
Type : EventEmitter
|
|
Should be a function to update the input data if undefined nothing will be triggered Sometimes it's useful to only define fetchData once. Example: Usage of multiple tables with data which is updated by the same function What happens: The function is triggered through one table and all tables will update |
updateSelection | |
Type : EventEmitter
|
|
This should be defined if you need access to the selection object. Each time the table selection changes, this will be triggered and the new selection object will be sent. |
_addTemplates |
_addTemplates()
|
Returns :
void
|
_calculateUniqueTableName | ||||
_calculateUniqueTableName(columns)
|
||||
Parameters :
Returns :
any
|
_initUserConfigAutoSave |
_initUserConfigAutoSave()
|
Returns :
void
|
_initUserConfigProxy | ||||
_initUserConfigProxy(observer)
|
||||
Parameters :
Returns :
void
|
_loadUserConfig |
_loadUserConfig()
|
Returns :
void
|
_saveUserConfig | ||||
_saveUserConfig(config)
|
||||
Parameters :
Returns :
void
|
basicDataSearch | ||||||||||||
basicDataSearch(searchTerm: string, rows: any[], columns: CdTableColumn[])
|
||||||||||||
Parameters :
Returns :
any
|
changeSorting | |||
changeSorting(undefined)
|
|||
Parameters :
Returns :
void
|
createSortingDefinition | ||||||
createSortingDefinition(prop: TableColumnProp)
|
||||||
Parameters :
Returns :
SortPropDir[]
|
filterHiddenColumns |
filterHiddenColumns()
|
Returns :
void
|
getRowClass |
getRowClass()
|
Returns :
() => { clickable: boolean; }
|
initUserConfig |
initUserConfig()
|
Returns :
void
|
ngAfterContentChecked |
ngAfterContentChecked()
|
Returns :
void
|
ngOnChanges |
ngOnChanges()
|
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Returns :
void
|
onSelect |
onSelect()
|
Returns :
void
|
refreshBtn |
refreshBtn()
|
Returns :
void
|
reloadData |
reloadData()
|
Returns :
void
|
reset |
reset()
|
Reset the data table to correct state. This includes: - Disable loading indicator - Reset 'Updating' flag
Returns :
void
|
rowIdentity |
rowIdentity()
|
Returns :
(row: any) => any
|
setLimit | ||||
setLimit(e)
|
||||
Parameters :
Returns :
void
|
subSearch | ||||||||||||
subSearch(data: any[], currentSearch: string[], columns: CdTableColumn[])
|
||||||||||||
Parameters :
Returns :
any
|
toggleColumn | ||||||
toggleColumn($event: any)
|
||||||
Parameters :
Returns :
void
|
updateColumns |
updateColumns()
|
Returns :
void
|
updateFilter | ||||||
updateFilter(clearSearch)
|
||||||
Parameters :
Returns :
void
|
updateSelected |
updateSelected()
|
After updating the data, we have to update the selected items because details may have changed, or some selected items may have been removed.
Returns :
void
|
updateUserColumns |
updateUserColumns()
|
Returns :
void
|
useCustomClass | ||||||
useCustomClass(value: any)
|
||||||
Parameters :
Returns :
string
|
useData |
useData()
|
Returns :
void
|
cellTemplates |
Type : literal type
|
Default value : {}
|
checkIconTpl |
Type : TemplateRef<any>
|
Decorators :
@ViewChild('checkIconTpl')
|
classAddingTpl |
Type : TemplateRef<any>
|
Decorators :
@ViewChild('classAddingTpl')
|
Private currentWidth |
Type : number
|
executingTpl |
Type : TemplateRef<any>
|
Decorators :
@ViewChild('executingTpl')
|
loadingError |
Default value : false
|
loadingIndicator |
Default value : true
|
localStorage |
Default value : window.localStorage
|
paginationClasses |
Type : object
|
Default value : {
pagerLeftArrow: 'i fa fa-angle-double-left',
pagerRightArrow: 'i fa fa-angle-double-right',
pagerPrevious: 'i fa fa-angle-left',
pagerNext: 'i fa fa-angle-right'
}
|
perSecondTpl |
Type : TemplateRef<any>
|
Decorators :
@ViewChild('perSecondTpl')
|
Private reloadSubscriber |
routerLinkTpl |
Type : TemplateRef<any>
|
Decorators :
@ViewChild('routerLinkTpl')
|
rows |
Type : []
|
Default value : []
|
Private saveSubscriber |
search |
Type : string
|
Default value : ''
|
selection |
Default value : new CdTableSelection()
|
Use this variable to access the selected row(s). |
sparklineTpl |
Type : TemplateRef<any>
|
Decorators :
@ViewChild('sparklineTpl')
|
table |
Type : DatatableComponent
|
Decorators :
@ViewChild(DatatableComponent)
|
tableCellBoldTpl |
Type : TemplateRef<any>
|
Decorators :
@ViewChild('tableCellBoldTpl')
|
tableColumns |
Type : CdTableColumn[]
|
tableName |
Type : string
|
Private updating |
Default value : false
|
userConfig |
Type : CdUserConfig
|
Default value : {}
|
import {
AfterContentChecked,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
NgZone,
OnChanges,
OnDestroy,
OnInit,
Output,
TemplateRef,
ViewChild
} from '@angular/core';
import {
DatatableComponent,
SortDirection,
SortPropDir,
TableColumnProp
} from '@swimlane/ngx-datatable';
import * as _ from 'lodash';
import { Observable, timer as observableTimer } from 'rxjs';
import { CellTemplate } from '../../enum/cell-template.enum';
import { CdTableColumn } from '../../models/cd-table-column';
import { CdTableFetchDataContext } from '../../models/cd-table-fetch-data-context';
import { CdTableSelection } from '../../models/cd-table-selection';
import { CdUserConfig } from '../../models/cd-user-config';
@Component({
selector: 'cd-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss']
})
export class TableComponent implements AfterContentChecked, OnInit, OnChanges, OnDestroy {
@ViewChild(DatatableComponent)
table: DatatableComponent;
@ViewChild('tableCellBoldTpl')
tableCellBoldTpl: TemplateRef<any>;
@ViewChild('sparklineTpl')
sparklineTpl: TemplateRef<any>;
@ViewChild('routerLinkTpl')
routerLinkTpl: TemplateRef<any>;
@ViewChild('checkIconTpl')
checkIconTpl: TemplateRef<any>;
@ViewChild('perSecondTpl')
perSecondTpl: TemplateRef<any>;
@ViewChild('executingTpl')
executingTpl: TemplateRef<any>;
@ViewChild('classAddingTpl')
classAddingTpl: TemplateRef<any>;
// This is the array with the items to be shown.
@Input()
data: any[];
// Each item -> { prop: 'attribute name', name: 'display name' }
@Input()
columns: CdTableColumn[];
// Each item -> { prop: 'attribute name', dir: 'asc'||'desc'}
@Input()
sorts?: SortPropDir[];
// Method used for setting column widths.
@Input()
columnMode? = 'flex';
// Display the tool header, including reload button, pagination and search fields?
@Input()
toolHeader? = true;
// Display the table header?
@Input()
header? = true;
// Display the table footer?
@Input()
footer? = true;
// Page size to show. Set to 0 to show unlimited number of rows.
@Input()
limit? = 10;
/**
* Auto reload time in ms - per default every 5s
* You can set it to 0, undefined or false to disable the auto reload feature in order to
* trigger 'fetchData' if the reload button is clicked.
*/
@Input()
autoReload: any = 5000;
// Which row property is unique for a row. If the identifier is not specified in any
// column, then the property name of the first column is used. Defaults to 'id'.
@Input()
identifier = 'id';
// If 'true', then the specified identifier is used anyway, although it is not specified
// in any column. Defaults to 'false'.
@Input()
forceIdentifier = false;
// Allows other components to specify which type of selection they want,
// e.g. 'single' or 'multi'.
@Input()
selectionType: string = undefined;
// By default selected item details will be updated on table refresh, if data has changed
@Input()
updateSelectionOnRefresh: 'always' | 'never' | 'onChange' = 'onChange';
@Input()
autoSave = true;
// Only needed to set if the classAddingTpl is used
@Input()
customCss?: { [css: string]: number | string | ((any) => boolean) };
/**
* Should be a function to update the input data if undefined nothing will be triggered
*
* Sometimes it's useful to only define fetchData once.
* Example:
* Usage of multiple tables with data which is updated by the same function
* What happens:
* The function is triggered through one table and all tables will update
*/
@Output()
fetchData = new EventEmitter();
/**
* This should be defined if you need access to the selection object.
*
* Each time the table selection changes, this will be triggered and
* the new selection object will be sent.
*
* @memberof TableComponent
*/
@Output()
updateSelection = new EventEmitter();
/**
* Use this variable to access the selected row(s).
*/
selection = new CdTableSelection();
tableColumns: CdTableColumn[];
cellTemplates: {
[key: string]: TemplateRef<any>;
} = {};
search = '';
rows = [];
loadingIndicator = true;
loadingError = false;
paginationClasses = {
pagerLeftArrow: 'i fa fa-angle-double-left',
pagerRightArrow: 'i fa fa-angle-double-right',
pagerPrevious: 'i fa fa-angle-left',
pagerNext: 'i fa fa-angle-right'
};
userConfig: CdUserConfig = {};
tableName: string;
localStorage = window.localStorage;
private saveSubscriber;
private reloadSubscriber;
private updating = false;
// Internal variable to check if it is necessary to recalculate the
// table columns after the browser window has been resized.
private currentWidth: number;
constructor(private ngZone: NgZone, private cdRef: ChangeDetectorRef) {}
ngOnInit() {
this._addTemplates();
if (!this.sorts) {
// Check whether the specified identifier exists.
const exists = _.findIndex(this.columns, ['prop', this.identifier]) !== -1;
// Auto-build the sorting configuration. If the specified identifier doesn't exist,
// then use the property of the first column.
this.sorts = this.createSortingDefinition(
exists ? this.identifier : this.columns[0].prop + ''
);
// If the specified identifier doesn't exist and it is not forced to use it anyway,
// then use the property of the first column.
if (!exists && !this.forceIdentifier) {
this.identifier = this.columns[0].prop + '';
}
}
this.initUserConfig();
this.columns.forEach((c) => {
if (c.cellTransformation) {
c.cellTemplate = this.cellTemplates[c.cellTransformation];
}
if (!c.flexGrow) {
c.flexGrow = c.prop + '' === this.identifier ? 1 : 2;
}
if (!c.resizeable) {
c.resizeable = false;
}
});
this.filterHiddenColumns();
// Load the data table content every N ms or at least once.
// Force showing the loading indicator if there are subscribers to the fetchData
// event. This is necessary because it has been set to False in useData() when
// this method was triggered by ngOnChanges().
if (this.fetchData.observers.length > 0) {
this.loadingIndicator = true;
}
if (_.isInteger(this.autoReload) && this.autoReload > 0) {
this.ngZone.runOutsideAngular(() => {
this.reloadSubscriber = observableTimer(0, this.autoReload).subscribe(() => {
this.ngZone.run(() => {
return this.reloadData();
});
});
});
} else {
this.reloadData();
}
}
initUserConfig() {
if (this.autoSave) {
this.tableName = this._calculateUniqueTableName(this.columns);
this._loadUserConfig();
this._initUserConfigAutoSave();
}
if (!this.userConfig.limit) {
this.userConfig.limit = this.limit;
}
if (!this.userConfig.sorts) {
this.userConfig.sorts = this.sorts;
}
if (!this.userConfig.columns) {
this.updateUserColumns();
} else {
this.columns.forEach((c, i) => {
c.isHidden = this.userConfig.columns[i].isHidden;
});
}
}
_calculateUniqueTableName(columns) {
const stringToNumber = (s) => {
if (!_.isString(s)) {
return 0;
}
let result = 0;
for (let i = 0; i < s.length; i++) {
result += s.charCodeAt(i) * i;
}
return result;
};
return columns
.reduce(
(result, value, index) =>
(stringToNumber(value.prop) + stringToNumber(value.name)) * (index + 1) + result,
0
)
.toString();
}
_loadUserConfig() {
const loaded = this.localStorage.getItem(this.tableName);
if (loaded) {
this.userConfig = JSON.parse(loaded);
}
}
_initUserConfigAutoSave() {
const source = Observable.create(this._initUserConfigProxy.bind(this));
this.saveSubscriber = source.subscribe(this._saveUserConfig.bind(this));
}
_initUserConfigProxy(observer) {
this.userConfig = new Proxy(this.userConfig, {
set(config, prop, value) {
config[prop] = value;
observer.next(config);
return true;
}
});
}
_saveUserConfig(config) {
this.localStorage.setItem(this.tableName, JSON.stringify(config));
}
updateUserColumns() {
this.userConfig.columns = this.columns.map((c) => ({
prop: c.prop,
name: c.name,
isHidden: !!c.isHidden
}));
}
filterHiddenColumns() {
this.tableColumns = this.columns.filter((c) => !c.isHidden);
}
ngOnDestroy() {
if (this.reloadSubscriber) {
this.reloadSubscriber.unsubscribe();
}
if (this.saveSubscriber) {
this.saveSubscriber.unsubscribe();
}
}
ngAfterContentChecked() {
// If the data table is not visible, e.g. another tab is active, and the
// browser window gets resized, the table and its columns won't get resized
// automatically if the tab gets visible again.
// https://github.com/swimlane/ngx-datatable/issues/193
// https://github.com/swimlane/ngx-datatable/issues/193#issuecomment-329144543
if (this.table && this.table.element.clientWidth !== this.currentWidth) {
this.currentWidth = this.table.element.clientWidth;
this.table.recalculate();
}
}
_addTemplates() {
this.cellTemplates.bold = this.tableCellBoldTpl;
this.cellTemplates.checkIcon = this.checkIconTpl;
this.cellTemplates.sparkline = this.sparklineTpl;
this.cellTemplates.routerLink = this.routerLinkTpl;
this.cellTemplates.perSecond = this.perSecondTpl;
this.cellTemplates.executing = this.executingTpl;
this.cellTemplates.classAdding = this.classAddingTpl;
}
useCustomClass(value: any): string {
if (!this.customCss) {
throw new Error('Custom classes are not set!');
}
const classes = Object.keys(this.customCss);
const css = Object.values(this.customCss)
.map((v, i) => ((_.isFunction(v) && v(value)) || v === value) && classes[i])
.filter((x) => x)
.join(' ');
return _.isEmpty(css) ? undefined : css;
}
ngOnChanges() {
this.useData();
}
setLimit(e) {
const value = parseInt(e.target.value, 10);
if (value > 0) {
this.userConfig.limit = value;
}
}
reloadData() {
if (!this.updating) {
this.loadingError = false;
const context = new CdTableFetchDataContext(() => {
// Do we have to display the error panel?
this.loadingError = context.errorConfig.displayError;
// Force data table to show no data?
if (context.errorConfig.resetData) {
this.data = [];
}
// Stop the loading indicator and reset the data table
// to the correct state.
this.useData();
});
this.fetchData.emit(context);
this.updating = true;
}
}
refreshBtn() {
this.loadingIndicator = true;
this.reloadData();
}
rowIdentity() {
return (row) => {
const id = row[this.identifier];
if (_.isUndefined(id)) {
throw new Error(`Wrong identifier "${this.identifier}" -> "${id}"`);
}
return id;
};
}
useData() {
if (!this.data) {
return; // Wait for data
}
this.rows = [...this.data];
if (this.search.length > 0) {
this.updateFilter();
}
this.reset();
this.updateSelected();
}
/**
* Reset the data table to correct state. This includes:
* - Disable loading indicator
* - Reset 'Updating' flag
*/
reset() {
this.loadingIndicator = false;
this.updating = false;
}
/**
* After updating the data, we have to update the selected items
* because details may have changed,
* or some selected items may have been removed.
*/
updateSelected() {
if (this.updateSelectionOnRefresh === 'never') {
return;
}
const newSelected = [];
this.selection.selected.forEach((selectedItem) => {
for (const row of this.data) {
if (selectedItem[this.identifier] === row[this.identifier]) {
newSelected.push(row);
}
}
});
if (
this.updateSelectionOnRefresh === 'onChange' &&
_.isEqual(this.selection.selected, newSelected)
) {
return;
}
this.selection.selected = newSelected;
this.onSelect();
}
onSelect() {
this.selection.update();
this.updateSelection.emit(_.clone(this.selection));
}
toggleColumn($event: any) {
const prop: TableColumnProp = $event.target.name;
const hide = !$event.target.checked;
if (hide && this.tableColumns.length === 1) {
$event.target.checked = true;
return;
}
_.find(this.columns, (c: CdTableColumn) => c.prop === prop).isHidden = hide;
this.updateColumns();
}
updateColumns() {
this.updateUserColumns();
this.filterHiddenColumns();
const sortProp = this.userConfig.sorts[0].prop;
if (!_.find(this.tableColumns, (c: CdTableColumn) => c.prop === sortProp)) {
this.userConfig.sorts = this.createSortingDefinition(this.tableColumns[0].prop);
this.table.onColumnSort({ sorts: this.userConfig.sorts });
}
this.table.recalculate();
this.cdRef.detectChanges();
}
createSortingDefinition(prop: TableColumnProp): SortPropDir[] {
return [
{
prop: prop,
dir: SortDirection.asc
}
];
}
changeSorting({ sorts }) {
this.userConfig.sorts = sorts;
}
updateFilter(clearSearch = false) {
if (clearSearch) {
this.search = '';
}
// prepare search strings
let search = this.search.toLowerCase().replace(/,/g, '');
const columns = this.columns.filter((c) => c.cellTransformation !== CellTemplate.sparkline);
if (search.match(/['"][^'"]+['"]/)) {
search = search.replace(/['"][^'"]+['"]/g, (match: string) => {
return match.replace(/(['"])([^'"]+)(['"])/g, '$2').replace(/ /g, '+');
});
}
// update the rows
this.rows = this.subSearch(this.data, search.split(' ').filter((s) => s.length > 0), columns);
// Whenever the filter changes, always go back to the first page
this.table.offset = 0;
}
subSearch(data: any[], currentSearch: string[], columns: CdTableColumn[]) {
if (currentSearch.length === 0 || data.length === 0) {
return data;
}
const searchTerms: string[] = currentSearch
.pop()
.replace('+', ' ')
.split(':');
const columnsClone = [...columns];
const dataClone = [...data];
const filterColumns = (columnName: string) =>
columnsClone.filter((c) => c.name.toLowerCase().indexOf(columnName) !== -1);
if (searchTerms.length === 2) {
columns = filterColumns(searchTerms[0]);
}
const searchTerm: string = _.last(searchTerms);
data = this.basicDataSearch(searchTerm, data, columns);
// Checks if user searches for column but he is still typing
if (data.length === 0 && searchTerms.length === 1 && filterColumns(searchTerm).length > 0) {
data = dataClone;
}
return this.subSearch(data, currentSearch, columnsClone);
}
basicDataSearch(searchTerm: string, rows: any[], columns: CdTableColumn[]) {
if (searchTerm.length === 0) {
return rows;
}
return rows.filter((row) => {
return (
columns.filter((col) => {
let cellValue: any = _.get(row, col.prop);
if (!_.isUndefined(col.pipe)) {
cellValue = col.pipe.transform(cellValue);
}
if (_.isUndefined(cellValue) || _.isNull(cellValue)) {
return false;
}
if (_.isArray(cellValue)) {
cellValue = cellValue.join(' ');
} else if (_.isNumber(cellValue) || _.isBoolean(cellValue)) {
cellValue = cellValue.toString();
}
return cellValue.toLowerCase().indexOf(searchTerm) !== -1;
}).length > 0
);
});
}
getRowClass() {
// Return the function used to populate a row's CSS classes.
return () => {
return {
clickable: !_.isUndefined(this.selectionType)
};
};
}
}
<cd-error-panel *ngIf="loadingError"
i18n>Failed to load data.</cd-error-panel>
<div class="dataTables_wrapper">
<div class="dataTables_header clearfix form-inline"
*ngIf="toolHeader">
<!-- actions -->
<div class="oadatatableactions">
<ng-content select=".table-actions"></ng-content>
</div>
<!-- end actions -->
<!-- filters -->
<ng-content select=".table-filters"></ng-content>
<!-- end filters -->
<!-- search -->
<div class="input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-search"></i>
</span>
<input class="form-control"
type="text"
[(ngModel)]="search"
(keyup)="updateFilter()">
<span class="input-group-btn">
<button type="button"
class="btn btn-default clear-input tc_clearInputBtn"
(click)="updateFilter(true)">
<i class="icon-prepend fa fa-remove"></i>
</button>
</span>
</div>
<!-- end search -->
<!-- pagination limit -->
<div class="input-group dataTables_paginate">
<input class="form-control"
type="number"
min="1"
max="9999"
[value]="userConfig.limit"
(click)="setLimit($event)"
(keyup)="setLimit($event)"
(blur)="setLimit($event)">
</div>
<!-- end pagination limit-->
<!-- show hide columns -->
<div class="widget-toolbar">
<div dropdown
class="dropdown tc_menuitem tc_menuitem_cluster">
<a dropdownToggle
class="btn btn-sm btn-default dropdown-toggle tc_columnBtn"
data-toggle="dropdown">
<i class="fa fa-lg fa-table"></i>
</a>
<ul *dropdownMenu
class="dropdown-menu">
<li *ngFor="let column of columns">
<label>
<input type="checkbox"
(change)="toggleColumn($event)"
[name]="column.prop"
[checked]="!column.isHidden">
<span>{{ column.name }}</span>
</label>
</li>
</ul>
</div>
</div>
<!-- end show hide columns -->
<!-- refresh button -->
<div class="widget-toolbar tc_refreshBtn"
*ngIf="fetchData.observers.length > 0">
<a (click)="refreshBtn()">
<i class="fa fa-lg fa-refresh"
[class.fa-spin]="updating || loadingIndicator"></i>
</a>
</div>
<!-- end refresh button -->
</div>
<ngx-datatable #table
class="bootstrap oadatatable"
[cssClasses]="paginationClasses"
[selectionType]="selectionType"
[selected]="selection.selected"
(select)="onSelect()"
[sorts]="userConfig.sorts"
(sort)="changeSorting($event)"
[columns]="tableColumns"
[columnMode]="columnMode"
[rows]="rows"
[rowClass]="getRowClass()"
[headerHeight]="header ? 'auto' : 0"
[footerHeight]="footer ? 'auto' : 0"
[limit]="userConfig.limit > 0 ? userConfig.limit : undefined"
[loadingIndicator]="loadingIndicator"
[rowIdentity]="rowIdentity()"
[rowHeight]="'auto'">
<ngx-datatable-footer>
<ng-template ngx-datatable-footer-template
let-rowCount="rowCount"
let-pageSize="pageSize"
let-selectedCount="selectedCount"
let-curPage="curPage"
let-offset="offset"
let-isVisible="isVisible">
<div class="page-count">
<span *ngIf="selectionType">
{{ selectedCount }} <ng-container i18n="X selected">selected</ng-container> /
</span>
<span *ngIf="rowCount != data?.length">
{{ rowCount }} <ng-container i18n="X found">found</ng-container> /
</span>
<span>
{{ data?.length || 0 }} <ng-container i18n="X total">total</ng-container>
</span>
</div>
<datatable-pager [pagerLeftArrowIcon]="paginationClasses.pagerLeftArrow"
[pagerRightArrowIcon]="paginationClasses.pagerRightArrow"
[pagerPreviousIcon]="paginationClasses.pagerPrevious"
[pagerNextIcon]="paginationClasses.pagerNext"
[page]="curPage"
[size]="pageSize"
[count]="rowCount"
[hidden]="!((rowCount / pageSize) > 1)"
(change)="table.onFooterPage($event)">
</datatable-pager>
</ng-template>
</ngx-datatable-footer>
</ngx-datatable>
</div>
<!-- Table Details -->
<ng-content select="[cdTableDetail]"></ng-content>
<!-- cell templates that can be accessed from outside -->
<ng-template #tableCellBoldTpl
let-value="value">
<strong>{{ value }}</strong>
</ng-template>
<ng-template #sparklineTpl
let-row="row"
let-value="value">
<cd-sparkline [data]="value"
[isBinary]="row.cdIsBinary"></cd-sparkline>
</ng-template>
<ng-template #routerLinkTpl
let-row="row"
let-value="value">
<a [routerLink]="[row.cdLink]"
[queryParams]="row.cdParams">{{ value }}</a>
</ng-template>
<ng-template #checkIconTpl
let-value="value">
<i class="fa fa-check fa-fw"
[hidden]="!value"></i>
</ng-template>
<ng-template #perSecondTpl
let-row="row"
let-value="value">
{{ value }} /s
</ng-template>
<ng-template #executingTpl
let-row="row"
let-value="value">
<i class="fa fa-spinner fa-spin fa-fw"
*ngIf="row.cdExecuting"></i>
{{ value }}
<span *ngIf="row.cdExecuting"
class="text-muted italic">({{ row.cdExecuting }}... )</span>
</ng-template>
<ng-template #classAddingTpl
let-value="value">
<span class="{{useCustomClass(value)}}">{{ value }}</span>
</ng-template>
./table.component.scss
@import '../../../../defaults';
.dataTables_wrapper {
margin-bottom: 25px;
.separator {
height: 30px;
border-left: 1px solid $color-table-seperator-border;
padding-left: 5px;
margin-left: 5px;
display: inline-block;
vertical-align: middle;
}
.widget-toolbar {
display: inline-block;
float: right;
width: auto;
height: 30px;
line-height: 28px;
position: relative;
border-left: 1px solid $color-table-seperator-border;
cursor: pointer;
padding: 0 8px;
text-align: center;
}
.dropdown-menu {
white-space: nowrap;
& > li {
cursor: pointer;
& > label {
width: 100%;
margin-bottom: 0;
padding-left: 20px;
padding-right: 20px;
cursor: pointer;
&:hover {
background-color: $color-table-dropdown-bg;
}
& > input {
cursor: pointer;
}
}
}
}
th.oadatatablecheckbox {
width: 16px;
}
.dataTables_length > input {
line-height: 25px;
text-align: right;
}
}
.dataTables_header {
background-color: $color-table-header-bg;
border: 1px solid $color-table-header-border;
border-bottom: none;
padding: 5px;
position: relative;
.oadatatableactions {
float: left;
}
::ng-deep .table-filters {
float: right;
border-left: 1px solid $color-table-seperator-border;
padding-left: 8px;
}
::ng-deep .table-filters label {
margin-right: 4px;
}
.form-group {
padding-left: 8px;
}
.input-group {
float: right;
border-left: 1px solid $color-table-input-border;
padding-left: 8px;
padding-right: 8px;
width: 40%;
max-width: 250px;
.form-control {
height: 30px;
}
.clear-input {
height: 30px;
i {
vertical-align: text-top;
}
}
}
.input-group.dataTables_paginate {
width: 8%;
min-width: 85px;
padding-right: 8px;
}
}
::ng-deep .oadatatable {
border: 1px solid $color-table-header-border;
margin-bottom: 0;
max-width: none !important;
.progress-linear {
display: block;
position: relative;
width: 100%;
height: 5px;
padding: 0;
margin: 0;
.container {
background-color: $color-table-active-row;
.bar {
left: 0;
height: 100%;
width: 100%;
position: absolute;
overflow: hidden;
background-color: $color-table-active-row;
}
.bar:before {
display: block;
position: absolute;
content: '';
left: -200px;
width: 200px;
height: 100%;
background-color: $color-table-progress-bar-active;
animation: progress-loading 3s linear infinite;
}
}
}
.datatable-header {
background-clip: padding-box;
background-color: $color-table-datatable-header;
background-image: -webkit-linear-gradient(
top,
$color-table-gradient-1 0,
$color-table-gradient-2 100%
);
background-image: -o-linear-gradient(
top,
$color-table-gradient-1 0,
$color-table-gradient-2 100%
);
background-image: linear-gradient(
to bottom,
$color-table-gradient-1 0,
$color-table-gradient-2 100%
);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);
.sort-asc,
.sort-desc {
color: $color-table-sort;
}
.datatable-header-cell {
@include table-cell;
text-align: left;
font-weight: bold;
.datatable-header-cell-label {
&:after {
font-family: ForkAwesome;
font-weight: 400;
height: 9px;
left: 10px;
line-height: 12px;
position: relative;
vertical-align: baseline;
width: 12px;
}
}
&.sortable {
.datatable-header-cell-label:after {
content: ' \f0dc';
}
&.sort-active {
&.sort-asc .datatable-header-cell-label:after {
content: ' \f160';
}
&.sort-desc .datatable-header-cell-label:after {
content: ' \f161';
}
}
}
&:first-child {
border-left: none;
}
}
}
.datatable-body {
.empty-row {
background-color: $color-table-empty-row;
text-align: center;
font-weight: bold;
font-style: italic;
padding-top: 5px;
padding-bottom: 5px;
}
.datatable-body-row {
.label {
font-size: 0.9em;
}
&.clickable:hover .datatable-row-group {
background-color: $color-table-hover-row;
transition-property: background;
transition-duration: 0.3s;
transition-timing-function: linear;
}
&.datatable-row-even {
background-color: $color-table-even-row-bg;
}
&.datatable-row-odd {
background-color: $color-table-odd-row-bg;
}
&.active,
&.active:hover {
background-color: $color-table-active-row-hover;
}
.datatable-body-cell {
@include table-cell;
&:first-child {
border-left: none;
}
.datatable-body-cell-label {
display: block;
}
}
}
}
.datatable-footer {
.selected-count,
.page-count {
font-style: italic;
padding-left: 5px;
}
.datatable-pager .pager {
margin-right: 5px !important;
& li:not(:first-child) {
/** Add space between buttons */
margin-left: 3px;
}
& li > a,
& li > span {
border-radius: 3px;
}
.pages {
& > a,
& > span {
display: inline-block;
padding: 5px 10px;
margin-bottom: 5px;
border: none;
}
a:hover {
background-color: $color-table-active-row;
}
&.active > a {
background-color: $color-table-active-row-hover;
}
}
}
}
}
@keyframes progress-loading {
from {
left: -200px;
width: 15%;
}
50% {
width: 30%;
}
70% {
width: 70%;
}
80% {
left: 50%;
}
95% {
left: 120%;
}
to {
left: 100%;
}
}