UNPKG

@bhplugin/ng-datatable

Version:

ng-datatable - fully customizable & easy to use datatable library

1 lines 100 kB
{"version":3,"file":"bhplugin-ng-datatable.mjs","sources":["../../lib/modals.ts","../../lib/slot.directive.ts","../../lib/column-filter.ts","../../lib/icon-check.ts","../../lib/icon-dash.ts","../../lib/icon-filter.ts","../../lib/column-header.ts","../../lib/icon-loader.ts","../../lib/ng-datatable.ts","../../lib/ng-datatable.html","../../lib/ng-datatable.module.ts","../../public-api.ts","../../bhplugin-ng-datatable.ts"],"sourcesContent":["export class colDef {\r\n isUnique?: boolean;\r\n field: string;\r\n title?: string;\r\n value?: any;\r\n condition?: any;\r\n type?: string; // string|date|number|bool\r\n width?: string | undefined;\r\n minWidth?: string | undefined;\r\n maxWidth?: string | undefined;\r\n hide?: boolean;\r\n filter?: boolean; // column filter\r\n search?: boolean; // global search\r\n sort?: boolean;\r\n html?: boolean;\r\n cellRenderer?: any; // [Function, string]\r\n headerClass?: string;\r\n cellClass?: string;\r\n}\r\n\r\nexport class Pager {\r\n public totalRows: number;\r\n public currentPage: number;\r\n public pageSize: number;\r\n public maxPage: number;\r\n public startPage: number;\r\n public endPage: number;\r\n public stringFormat: string;\r\n public pages: any;\r\n}\r\n","import { TemplateRef, Directive, Input } from '@angular/core';\r\n\r\n@Directive({\r\n selector: '[slot]',\r\n})\r\nexport class SlotDirective {\r\n @Input('slot') fieldName: string;\r\n @Input('slotValue') value: any;\r\n\r\n constructor(public templateRef: TemplateRef<any>) {}\r\n}\r\n","import { Component, Input, Output, EventEmitter } from '@angular/core';\r\n\r\n@Component({\r\n selector: 'column-filter',\r\n template: `\r\n <div class=\"bh-filter-menu bh-absolute bh-z-[1] bh-bg-white bh-shadow-md bh-rounded-md bh-top-full bh-right-0 bh-w-32 bh-mt-1\">\r\n <div class=\"bh-text-[13px] bh-font-normal bh-rounded bh-overflow-hidden\" (click)=\"closeMethod(); $event.stopPropagation()\">\r\n <button type=\"button\" [class.active]=\"column.condition === ''\" (click)=\"select('')\">No filter</button>\r\n <ng-container *ngIf=\"column.type === 'string'\">\r\n <button type=\"button\" [class.active]=\"column.condition === 'contain'\" (click)=\"select('contain')\">Contain</button>\r\n <button type=\"button\" [class.active]=\"column.condition === 'not_contain'\" (click)=\"select('not_contain')\">Not contain</button>\r\n <button type=\"button\" [class.active]=\"column.condition === 'equal'\" (click)=\"select('equal')\">Equal</button>\r\n <button type=\"button\" [class.active]=\"column.condition === 'not_equal'\" (click)=\"select('not_equal')\">Not equal</button>\r\n <button type=\"button\" [class.active]=\"column.condition === 'start_with'\" (click)=\"select('start_with')\">Starts with</button>\r\n <button type=\"button\" [class.active]=\"column.condition === 'end_with'\" (click)=\"select('end_with')\">Ends with</button>\r\n </ng-container>\r\n <ng-container *ngIf=\"column.type === 'number'\">\r\n <button type=\"button\" [class.active]=\"column.condition === 'equal'\" (click)=\"select('equal')\">Equal</button>\r\n <button type=\"button\" [class.active]=\"column.condition === 'not_equal'\" (click)=\"select('not_equal')\">Not Equal</button>\r\n <button type=\"button\" [class.active]=\"column.condition === 'greater_than'\" (click)=\"select('greater_than')\">Greater than</button>\r\n <button type=\"button\" [class.active]=\"column.condition === 'greater_than_equal'\" (click)=\"select('greater_than_equal')\">Greater than or equal</button>\r\n <button type=\"button\" [class.active]=\"column.condition === 'less_than'\" (click)=\"select('less_than')\">Less than</button>\r\n <button type=\"button\" [class.active]=\"column.condition === 'less_than_equal'\" (click)=\"select('less_than_equal')\">Less than or equal</button>\r\n </ng-container>\r\n <ng-container *ngIf=\"column.type === 'date'\">\r\n <button type=\"button\" [ngClass]=\"{ active: column.condition === 'equal' }\" (click)=\"select('equal')\">Equal</button>\r\n <button type=\"button\" [ngClass]=\"{ active: column.condition === 'not_equal' }\" (click)=\"select('not_equal')\">Not equal</button>\r\n <button type=\"button\" [ngClass]=\"{ active: column.condition === 'greater_than' }\" (click)=\"select('greater_than')\">Greater than</button>\r\n <button type=\"button\" [ngClass]=\"{ active: column.condition === 'less_than' }\" (click)=\"select('less_than')\">Less than</button>\r\n </ng-container>\r\n\r\n <button type=\"button\" [ngClass]=\"{ active: column.condition === 'is_null' }\" (click)=\"select('is_null')\">Is null</button>\r\n <button type=\"button\" [ngClass]=\"{ active: column.condition === 'is_not_null' }\" (click)=\"select('is_not_null')\">Not null</button>\r\n </div>\r\n </div>\r\n `,\r\n})\r\nexport class ColumnFilterComponent {\r\n @Input() column: any;\r\n @Output('close') close: EventEmitter<any> = new EventEmitter<any>();\r\n @Output('filterChange') filterChange: EventEmitter<any> = new EventEmitter<any>();\r\n constructor() {}\r\n\r\n ngOnInit() {\r\n document.addEventListener('click', this.closeMethod.bind(this));\r\n }\r\n\r\n ngOnDestroy() {\r\n document.removeEventListener('click', this.closeMethod.bind(this));\r\n }\r\n\r\n closeMethod() {\r\n this.close.emit();\r\n }\r\n\r\n select(condition: any) {\r\n this.column.condition = condition;\r\n if (condition === '') {\r\n this.column.value = '';\r\n }\r\n\r\n this.filterChange.emit(this.column);\r\n }\r\n}\r\n","import { Component, Input, ViewChild, ViewContainerRef } from '@angular/core';\r\n@Component({\r\n selector: 'icon-check',\r\n template: `\r\n <ng-template #template>\r\n <svg [ngClass]=\"class\" version=\"1.1\" viewBox=\"0 0 17 12\">\r\n <g fill=\"none\" fill-rule=\"evenodd\">\r\n <g transform=\"translate(-9 -11)\" fill=\"currentColor\" fill-rule=\"nonzero\">\r\n <path\r\n d=\"m25.576 11.414c0.56558 0.55188 0.56558 1.4439 0 1.9961l-9.404 9.176c-0.28213 0.27529-0.65247 0.41385-1.0228 0.41385-0.37034 0-0.74068-0.13855-1.0228-0.41385l-4.7019-4.588c-0.56584-0.55188-0.56584-1.4442 0-1.9961 0.56558-0.55214 1.4798-0.55214 2.0456 0l3.679 3.5899 8.3812-8.1779c0.56558-0.55214 1.4798-0.55214 2.0456 0z\"\r\n />\r\n </g>\r\n </g>\r\n </svg>\r\n </ng-template>\r\n `,\r\n})\r\nexport class IconCheckComponent {\r\n @ViewChild('template', { static: true }) template: any;\r\n @Input() class: any;\r\n constructor(private viewContainerRef: ViewContainerRef) {}\r\n ngOnInit() {\r\n this.viewContainerRef.createEmbeddedView(this.template);\r\n }\r\n}\r\n","import { Component, Input, ViewChild, ViewContainerRef } from '@angular/core';\r\n@Component({\r\n selector: 'icon-dash',\r\n template: `\r\n <ng-template #template>\r\n <svg [ngClass]=\"class\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" stroke=\"currentColor\" stroke-width=\"3\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line>\r\n </svg>\r\n </ng-template>\r\n `,\r\n})\r\nexport class IconDashComponent {\r\n @ViewChild('template', { static: true }) template: any;\r\n @Input() class: any;\r\n constructor(private viewContainerRef: ViewContainerRef) {}\r\n ngOnInit() {\r\n this.viewContainerRef.createEmbeddedView(this.template);\r\n }\r\n}\r\n","import { Component, Input, ViewChild, ViewContainerRef } from '@angular/core';\r\n@Component({\r\n selector: 'icon-filter',\r\n template: `\r\n <ng-template #template>\r\n <svg [ngClass]=\"class\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" stroke=\"currentColor\" stroke-width=\"1.5\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <polygon points=\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\"></polygon>\r\n </svg>\r\n </ng-template>\r\n `,\r\n})\r\nexport class IconFilterComponent {\r\n @ViewChild('template', { static: true }) template: any;\r\n @Input() class: any;\r\n constructor(private viewContainerRef: ViewContainerRef) {}\r\n ngOnInit() {\r\n this.viewContainerRef.createEmbeddedView(this.template);\r\n }\r\n}\r\n","import { Component, Input, Output, EventEmitter, ViewChild, ViewContainerRef } from '@angular/core';\r\nimport { colDef } from './modals';\r\n\r\n@Component({\r\n selector: 'column-header',\r\n template: `\r\n <ng-template #template>\r\n <tr>\r\n <th\r\n *ngIf=\"all.hasCheckbox\"\r\n [class]=\"'bh-w-px'\"\r\n [ngClass]=\"{\r\n 'bh-sticky bh-bg-blue-light bh-z-[1]': all.stickyHeader || all.stickyFirstColumn,\r\n 'bh-top-0': all.stickyHeader,\r\n 'bh-left-0': all.stickyFirstColumn\r\n }\"\r\n >\r\n <div class=\"bh-checkbox\">\r\n <input #selectedAll type=\"checkbox\" (click)=\"selectAll.emit(selectedAll.checked); $event.stopPropagation()\" />\r\n <div>\r\n <icon-check class=\"check\"></icon-check>\r\n <icon-dash class=\"intermediate\"></icon-dash>\r\n </div>\r\n </div>\r\n </th>\r\n\r\n <ng-container *ngFor=\"let col of all.columns; let j = index\">\r\n <th\r\n *ngIf=\"!col.hide\"\r\n [class]=\"'bh-select-none bh-z-[1]'\"\r\n [ngClass]=\"[\r\n all.sortable && col.sort ? 'bh-cursor-pointer' : '',\r\n j === 0 && all.stickyFirstColumn ? 'bh-sticky bh-left-0 bh-bg-blue-light' : '',\r\n all.hasCheckbox && j === 0 && all.stickyFirstColumn ? 'bh-left-[52px]' : ''\r\n ]\"\r\n [style]=\"{ width: col.width, 'min-width': col.minWidth, 'max-width': col.maxWidth }\"\r\n >\r\n <div class=\"bh-flex bh-items-center\" [ngClass]=\"[col.headerClass ? col.headerClass : '']\" (click)=\"all.sortable && col.sort && sortChange.emit(col.field)\">\r\n {{ col.title }}\r\n <span *ngIf=\"all.sortable && col.sort\" class=\"bh-ml-3 bh-sort bh-flex bh-items-center\" [ngClass]=\"[all.sortColumn, all.sortDirection]\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 14 14\" fill=\"none\">\r\n <polygon\r\n points=\"3.11,6.25 10.89,6.25 7,1.75\"\r\n fill=\"currentColor\"\r\n class=\"bh-text-black/20\"\r\n [ngClass]=\"[all.sortColumn === col.field && all.sortDirection === 'asc' ? '!bh-text-primary' : '']\"\r\n ></polygon>\r\n <polygon\r\n points=\"7,12.25 10.89,7.75 3.11,7.75\"\r\n fill=\"currentColor\"\r\n class=\"bh-text-black/20\"\r\n [ngClass]=\"[all.sortColumn === col.field && all.sortDirection === 'desc' ? '!bh-text-primary' : '']\"\r\n ></polygon>\r\n </svg>\r\n </span>\r\n </div>\r\n\r\n <ng-container *ngIf=\"all.columnFilter && !isFooter\">\r\n <div *ngIf=\"col.filter\" class=\"bh-filter bh-relative\">\r\n <input *ngIf=\"col.type === 'string'\" [(ngModel)]=\"col.value\" type=\"text\" class=\"bh-form-control\" (keyup)=\"filterChange.emit()\" />\r\n <input *ngIf=\"col.type === 'number'\" [(ngModel)]=\"col.value\" type=\"number\" class=\"bh-form-control\" (keyup)=\"filterChange.emit()\" />\r\n <input *ngIf=\"col.type === 'date'\" [(ngModel)]=\"col.value\" type=\"date\" class=\"bh-form-control\" (change)=\"filterChange.emit()\" />\r\n <select *ngIf=\"col.type === 'bool'\" [(ngModel)]=\"col.value\" class=\"bh-form-control\" (change)=\"filterChange.emit()\" (click)=\"isOpenFilter = null\">\r\n <option [ngValue]=\"undefined\">All</option>\r\n <option [ngValue]=\"true\">True</option>\r\n <option [ngValue]=\"false\">False</option>\r\n </select>\r\n\r\n <button *ngIf=\"col.type !== 'bool'\" type=\"button\" (click)=\"toggleFilterMenu(col); $event.stopPropagation()\">\r\n <icon-filter class=\"bh-w-4\"></icon-filter>\r\n </button>\r\n\r\n <column-filter\r\n [ngClass]=\"{ 'bh-hidden': isOpenFilter !== col.field }\"\r\n [column]=\"col\"\r\n (close)=\"toggleFilterMenu()\"\r\n (filterChange)=\"filterChange.emit()\"\r\n ></column-filter>\r\n </div>\r\n </ng-container>\r\n </th>\r\n </ng-container>\r\n </tr>\r\n </ng-template>\r\n `,\r\n})\r\nexport class ColumnHeaderComponent {\r\n @ViewChild('template', { static: true }) template: any;\r\n @ViewChild('selectedAll') selectedAll: any;\r\n\r\n @Input() all: any;\r\n @Input() isFooter: any;\r\n @Input() checkAll: any;\r\n\r\n @Output('selectAll') selectAll: EventEmitter<any> = new EventEmitter<any>();\r\n @Output('sortChange') sortChange: EventEmitter<any> = new EventEmitter<any>();\r\n @Output('filterChange') filterChange: EventEmitter<any> = new EventEmitter<any>();\r\n\r\n isOpenFilter: any = null;\r\n\r\n constructor(private viewContainerRef: ViewContainerRef) {}\r\n\r\n ngOnInit() {\r\n this.viewContainerRef.createEmbeddedView(this.template);\r\n this.checkboxChange();\r\n }\r\n\r\n checkboxChange() {\r\n if (this.selectedAll) {\r\n this.selectedAll.nativeElement.indeterminate = this.checkAll !== 0 ? !this.checkAll : false;\r\n this.selectedAll.nativeElement.checked = this.checkAll;\r\n }\r\n }\r\n\r\n toggleFilterMenu(col?: colDef) {\r\n if (col) {\r\n if (this.isOpenFilter === col.field) {\r\n this.isOpenFilter = null;\r\n } else {\r\n this.isOpenFilter = col.field;\r\n }\r\n } else {\r\n this.isOpenFilter = null;\r\n }\r\n }\r\n}\r\n","import { Component } from '@angular/core';\r\n@Component({\r\n selector: 'icon-loader',\r\n template: `\r\n <svg width=\"84\" height=\"84\" viewBox=\"0 0 24 24\" class=\"bh-loader bh-text-primary\">\r\n <circle cx=\"18\" cy=\"12\" r=\"0\" fill=\"currentColor\">\r\n <animate\r\n attributeName=\"r\"\r\n begin=\".67\"\r\n calcMode=\"spline\"\r\n dur=\"1.5s\"\r\n keySplines=\"0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8\"\r\n repeatCount=\"indefinite\"\r\n values=\"0;2;0;0\"\r\n />\r\n </circle>\r\n <circle cx=\"12\" cy=\"12\" r=\"0\" fill=\"currentColor\">\r\n <animate\r\n attributeName=\"r\"\r\n begin=\".33\"\r\n calcMode=\"spline\"\r\n dur=\"1.5s\"\r\n keySplines=\"0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8\"\r\n repeatCount=\"indefinite\"\r\n values=\"0;2;0;0\"\r\n />\r\n </circle>\r\n <circle cx=\"6\" cy=\"12\" r=\"0\" fill=\"currentColor\">\r\n <animate\r\n attributeName=\"r\"\r\n begin=\"0\"\r\n calcMode=\"spline\"\r\n dur=\"1.5s\"\r\n keySplines=\"0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8\"\r\n repeatCount=\"indefinite\"\r\n values=\"0;2;0;0\"\r\n />\r\n </circle>\r\n </svg>\r\n `,\r\n})\r\nexport class IconLoaderComponent {\r\n constructor() {}\r\n}\r\n","import {\n ChangeDetectionStrategy,\n Component,\n ContentChildren,\n EventEmitter,\n Input,\n Output,\n QueryList,\n SimpleChanges,\n TemplateRef,\n ViewChild,\n ViewEncapsulation,\n} from '@angular/core';\nimport { Pager, colDef } from './modals';\nimport { SlotDirective } from './slot.directive';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\n\n@Component({\n selector: 'ng-datatable',\n templateUrl: './ng-datatable.html',\n styleUrls: ['./style.css'],\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NgDataTableComponent {\n // props\n @Input() loading: boolean = false;\n @Input() isServerMode: boolean = false;\n @Input() skin: string = 'bh-table-striped bh-table-hover';\n @Input() totalRows: number = 0;\n @Input() rows: Array<any> = [];\n @Input() columns: Array<colDef> = [];\n @Input() hasCheckbox: boolean = false;\n @Input() search: string = '';\n @Input() page: number = 1;\n @Input() pageSize: number = 10;\n @Input() pageSizeOptions: Array<number> = [10, 20, 30, 50, 100];\n @Input() showPageSize: boolean = true;\n @Input() rowClass: string | Function = '';\n @Input() cellClass: string | Function = '';\n @Input() sortable: boolean = false;\n @Input() sortColumn: string = 'id';\n @Input() sortDirection: string = 'asc';\n @Input() columnFilter: boolean = false;\n @Input() pagination: boolean = true;\n @Input() showNumbers: boolean = true;\n @Input() showNumbersCount: number = 5;\n @Input() showFirstPage: boolean = true;\n @Input() showLastPage: boolean = true;\n @Input() firstArrow: string = '';\n @Input() lastArrow: string = '';\n @Input() nextArrow: string = '';\n @Input() previousArrow: string = '';\n @Input() paginationInfo: string = 'Showing {0} to {1} of {2} entries';\n @Input() noDataContent: string = 'No data available';\n @Input() stickyHeader: boolean = false;\n @Input() height: string = '500px';\n @Input() stickyFirstColumn: boolean = false;\n @Input() cloneHeaderInFooter: boolean = false;\n @Input() selectRowOnClick: boolean = false;\n @Input() showFooterRow: boolean = false;\n\n // events\n @Output() changeServer = new EventEmitter<any>();\n @Output() sortChange = new EventEmitter<any>();\n @Output() searchChange = new EventEmitter<any>();\n @Output() pageChange = new EventEmitter<any>();\n @Output() pageSizeChange = new EventEmitter<any>();\n @Output() rowSelect = new EventEmitter<any>();\n @Output() filterChange = new EventEmitter<any>();\n @Output() rowClick = new EventEmitter<any>();\n @Output() rowDBClick = new EventEmitter<any>();\n\n // variables\n filterItems: Array<any> = [];\n currentPage = this.page;\n currentPageSize = this.pagination ? this.pageSize : this.rows.length;\n oldPageSize = this.pageSize;\n currentSortColumn = this.sortColumn;\n oldSortColumn = this.sortColumn;\n currentSortDirection = this.sortDirection;\n oldSortDirection = this.sortDirection;\n filterRowCount = this.totalRows;\n\n selectedAll: any = null;\n currentLoader = this.loading;\n currentSearch = this.search;\n oldColumns: colDef[];\n uniqueKey: string = '';\n\n constructor(private sanitizer: DomSanitizer) {}\n ngOnInit() {\n this.initDeafultValues();\n this.changeRows();\n }\n ngOnChanges(changes: SimpleChanges) {\n // watch loading\n if (changes['loading'] && !changes['loading'].firstChange) {\n this.currentLoader = this.loading;\n }\n\n // watch rows and columns\n if ((changes['rows'] && !changes['rows'].firstChange) || (changes['columns'] && !changes['columns'].firstChange)) {\n if (!this.isServerMode) {\n this.currentPage = 1;\n this.oldColumns = this.noReact(this.columns);\n }\n this.changeRows();\n }\n\n // watch page\n if (!this.isServerMode) {\n if (changes['page'] && !changes['page'].firstChange) {\n this.movePage(this.page);\n }\n }\n\n // watch pagesize\n if (changes['pageSize'] && !changes['pageSize'].firstChange) {\n this.currentPageSize = this.pagination ? this.pageSize : this.rows.length;\n if (!this.isServerMode) {\n this.changePageSize();\n }\n }\n\n // watch search\n if (changes['search'] && !changes['search'].firstChange) {\n this.currentSearch = this.search;\n this.changeSearch();\n }\n\n // watch sort column and direction\n if (!this.isServerMode) {\n if ((changes['sortColumn'] && !changes['sortColumn'].firstChange) || (changes['sortDirection'] && !changes['sortDirection'].firstChange)) {\n this.sortChangeMethod(this.sortColumn, this.sortDirection);\n }\n }\n }\n\n initDeafultValues() {\n this.currentLoader = this.loading;\n this.currentSearch = this.search;\n this.currentSortColumn = this.sortColumn;\n this.currentSortDirection = this.sortDirection;\n this.filterRowCount = this.totalRows;\n\n this.currentPage = this.page;\n if (this.pagination) {\n const exists = this.pageSizeOptions.find((d) => d === this.pageSize);\n this.currentPageSize = exists ? this.pageSize : 10;\n } else {\n this.currentPageSize = this.rows.length;\n }\n\n this.oldPageSize = this.pageSize;\n this.oldSortColumn = this.sortColumn;\n this.oldSortDirection = this.sortDirection;\n\n // set default columns values\n for (const item of this.columns || []) {\n const type = item.type?.toLowerCase() || 'string';\n item.type = type;\n item.isUnique = item.isUnique !== undefined ? item.isUnique : false;\n item.hide = item.hide !== undefined ? item.hide : false;\n item.filter = item.filter !== undefined ? item.filter : true;\n item.search = item.search !== undefined ? item.search : true;\n item.sort = item.sort !== undefined ? item.sort : true;\n item.html = item.html !== undefined ? item.html : false;\n item.condition = !type || type === 'string' ? 'contain' : 'equal';\n }\n\n this.oldColumns = this.noReact(this.columns);\n\n this.setUniqueKey();\n }\n props: any;\n get getProps() {\n return {\n loading: this.currentLoader,\n isServerMode: this.isServerMode,\n skin: this.skin,\n totalRows: this.filterRowCount,\n rows: this.rows,\n columns: this.columns,\n hasCheckbox: this.hasCheckbox,\n search: this.currentSearch,\n page: this.currentPage,\n pageSize: this.currentPageSize,\n pageSizeOptions: this.pageSizeOptions,\n showPageSize: this.showPageSize,\n rowClass: this.rowClass,\n cellClass: this.cellClass,\n sortable: this.sortable,\n sortColumn: this.currentSortColumn,\n sortDirection: this.currentSortDirection,\n columnFilter: this.columnFilter,\n pagination: this.pagination,\n showNumbers: this.showNumbers,\n showNumbersCount: this.showNumbersCount,\n showFirstPage: this.showFirstPage,\n showLastPage: this.showLastPage,\n firstArrow: this.firstArrow,\n lastArrow: this.lastArrow,\n nextArrow: this.nextArrow,\n previousArrow: this.previousArrow,\n paginationInfo: this.paginationInfo,\n noDataContent: this.noDataContent,\n stickyHeader: this.stickyHeader,\n height: this.height,\n stickyFirstColumn: this.stickyFirstColumn,\n cloneHeaderInFooter: this.cloneHeaderInFooter,\n selectRowOnClick: this.selectRowOnClick,\n showFooterRow: this.showFooterRow,\n };\n }\n\n isFunction(value: any): value is Function {\n return typeof value === 'function';\n }\n\n stringFormat() {\n const args: any[] = [this.filterRowCount ? this.offset() : 0, this.limit(), this.filterRowCount];\n return this.paginationInfo.replace(/{(\\d+)}/g, (match, number) => {\n return typeof args[number] != 'undefined' ? args[number] : match;\n });\n }\n setUniqueKey() {\n const col = this.columns.find((d) => d.isUnique);\n this.uniqueKey = col?.field || '';\n }\n\n maxPage() {\n const totalPages = this.currentPageSize < 1 ? 1 : Math.ceil(this.filterRowCount / this.currentPageSize);\n return Math.max(totalPages || 0, 1);\n }\n offset() {\n return (this.currentPage - 1) * this.currentPageSize + 1;\n }\n limit() {\n const limit = this.currentPage * this.currentPageSize;\n return this.filterRowCount >= limit ? limit : this.filterRowCount;\n }\n\n pager: Pager = new Pager();\n getPager() {\n // total pages\n let totalPages = this.maxPage();\n\n // pages\n let startPage: number, endPage: number;\n let isMaxSized = typeof this.showNumbersCount !== 'undefined' && this.showNumbersCount < totalPages;\n // recompute if maxSize\n if (isMaxSized) {\n // Current page is displayed in the middle of the visible ones\n startPage = Math.max(this.currentPage - Math.floor(this.showNumbersCount / 2), 1);\n endPage = startPage + this.showNumbersCount - 1;\n\n // Adjust if limit is exceeded\n if (endPage > totalPages) {\n endPage = totalPages;\n startPage = endPage - this.showNumbersCount + 1;\n }\n } else {\n startPage = 1;\n endPage = totalPages;\n }\n\n const pages = Array.from(Array(endPage + 1 - startPage).keys()).map((i) => startPage + i);\n\n return <Pager>{\n totalRows: this.isServerMode ? this.totalRows : this.filterItems.length,\n currentPage: this.currentPage,\n pageSize: this.pageSize,\n maxPage: totalPages,\n startPage: startPage,\n endPage: endPage,\n stringFormat: this.stringFormat(),\n pages: pages,\n };\n }\n setPager() {\n this.pager = this.getPager();\n }\n\n filterRows() {\n let result = [];\n let rows = this.rows || [];\n\n if (this.isServerMode) {\n this.filterRowCount = this.totalRows || 0;\n result = rows;\n } else {\n this.columns?.forEach((d) => {\n if (d.filter && ((d.value !== undefined && d.value !== null && d.value !== '') || d.condition === 'is_null' || d.condition === 'is_not_null')) {\n // string filters\n if (d.type === 'string') {\n if (d.value && !d.condition) {\n d.condition = 'contain';\n }\n\n if (d.condition === 'contain') {\n rows = rows.filter((item) => {\n return this.cellValue(item, d.field)?.toString().toLowerCase().includes(d.value.toLowerCase());\n });\n } else if (d.condition === 'not_contain') {\n rows = rows.filter((item) => {\n return !this.cellValue(item, d.field)?.toString().toLowerCase().includes(d.value.toLowerCase());\n });\n } else if (d.condition === 'equal') {\n rows = rows.filter((item) => {\n return this.cellValue(item, d.field)?.toString().toLowerCase() === d.value.toLowerCase();\n });\n } else if (d.condition === 'not_equal') {\n rows = rows.filter((item) => {\n return this.cellValue(item, d.field)?.toString().toLowerCase() !== d.value.toLowerCase();\n });\n } else if (d.condition === 'start_with') {\n rows = rows.filter((item) => {\n return this.cellValue(item, d.field)?.toString().toLowerCase().indexOf(d.value.toLowerCase()) === 0;\n });\n } else if (d.condition === 'end_with') {\n rows = rows.filter((item) => {\n return (\n this.cellValue(item, d.field)\n ?.toString()\n .toLowerCase()\n .substr(d.value.length * -1) === d.value.toLowerCase()\n );\n });\n }\n }\n\n // number filters\n else if (d.type === 'number') {\n if (d.value && !d.condition) {\n d.condition = 'equal';\n }\n\n if (d.condition === 'equal') {\n rows = rows.filter((item) => {\n return this.cellValue(item, d.field) && parseFloat(this.cellValue(item, d.field)) === parseFloat(d.value);\n });\n } else if (d.condition === 'not_equal') {\n rows = rows.filter((item) => {\n return this.cellValue(item, d.field) && parseFloat(this.cellValue(item, d.field)) !== parseFloat(d.value);\n });\n } else if (d.condition === 'greater_than') {\n rows = rows.filter((item) => {\n return this.cellValue(item, d.field) && parseFloat(this.cellValue(item, d.field)) > parseFloat(d.value);\n });\n } else if (d.condition === 'greater_than_equal') {\n rows = rows.filter((item) => {\n return this.cellValue(item, d.field) && parseFloat(this.cellValue(item, d.field)) >= parseFloat(d.value);\n });\n } else if (d.condition === 'less_than') {\n rows = rows.filter((item) => {\n return this.cellValue(item, d.field) && parseFloat(this.cellValue(item, d.field)) < parseFloat(d.value);\n });\n } else if (d.condition === 'less_than_equal') {\n rows = rows.filter((item) => {\n return this.cellValue(item, d.field) && parseFloat(this.cellValue(item, d.field)) <= parseFloat(d.value);\n });\n }\n }\n\n // date filters\n if (d.type === 'date') {\n if (d.value && !d.condition) {\n d.condition = 'equal';\n }\n\n if (d.condition === 'equal') {\n rows = rows.filter((item: any) => {\n return this.cellValue(item, d.field) && this.dateFormat(this.cellValue(item, d.field)) === d.value;\n });\n } else if (d.condition === 'not_equal') {\n rows = rows.filter((item: any) => {\n return this.cellValue(item, d.field) && this.dateFormat(this.cellValue(item, d.field)) !== d.value;\n });\n } else if (d.condition === 'greater_than') {\n rows = rows.filter((item: any) => {\n return this.cellValue(item, d.field) && this.dateFormat(this.cellValue(item, d.field)) > d.value;\n });\n } else if (d.condition === 'less_than') {\n rows = rows.filter((item: any) => {\n return this.cellValue(item, d.field) && this.dateFormat(this.cellValue(item, d.field)) < d.value;\n });\n }\n }\n\n // boolean filters\n else if (d.type === 'bool') {\n rows = rows.filter((item: any) => {\n return this.cellValue(item, d.field) === d.value;\n });\n }\n\n if (d.condition === 'is_null') {\n rows = rows.filter((item: any) => {\n return this.cellValue(item, d.field) == null || this.cellValue(item, d.field) === '';\n });\n d.value = '';\n } else if (d.condition === 'is_not_null') {\n d.value = '';\n rows = rows.filter((item: any) => {\n return this.cellValue(item, d.field);\n });\n }\n }\n });\n\n if (this.currentSearch && rows.length) {\n let final: Array<any> = [];\n\n const keys = (this.columns || [])\n .filter((d: any) => d.search && !d.hide)\n .map((d: any) => {\n return d.field;\n });\n\n for (let j = 0; j < rows.length; j++) {\n for (let i = 0; i < keys.length; i++) {\n if (this.cellValue(rows[j], keys[i])?.toString().toLowerCase().includes(this.currentSearch.toLowerCase())) {\n final.push(rows[j]);\n break;\n }\n }\n }\n\n rows = final;\n }\n\n // sort rows\n const collator = new Intl.Collator(undefined, {\n numeric: true,\n sensitivity: 'base',\n });\n const sortOrder = this.currentSortDirection === 'desc' ? -1 : 1;\n\n rows.sort((a: any, b: any): number => {\n const valA = this.currentSortColumn?.split('.').reduce((obj, key) => obj?.[key], a);\n const valB = this.currentSortColumn?.split('.').reduce((obj, key) => obj?.[key], b);\n\n return collator.compare(valA, valB) * sortOrder;\n });\n\n this.filterRowCount = rows.length || 0;\n result = rows.slice(this.offset() - 1, <number>this.limit());\n }\n\n this.filterItems = result || [];\n\n this.setPager();\n }\n\n // page change\n movePage(page: number = 1) {\n if (this.currentLoader || page < 1 || page > this.maxPage()) {\n return;\n }\n this.currentPage = page;\n this.clearSelectedRows();\n\n if (this.isServerMode) {\n this.changeForServer('page');\n } else {\n this.filterRows();\n this.pageChange.emit(this.currentPage);\n }\n }\n\n // row update\n changeRows() {\n this.clearSelectedRows();\n this.filterRows();\n }\n\n // pagesize changed\n changePageSize() {\n this.currentPage = 1;\n\n this.clearSelectedRows();\n\n if (this.isServerMode) {\n this.changeForServer('pagesize', true);\n } else {\n this.filterRows();\n this.pageSizeChange.emit(this.currentPageSize);\n }\n }\n\n // sorting\n sortChangeMethod(field: string, dir = '') {\n let direction = dir || 'asc';\n if (field === this.currentSortColumn) {\n if (this.currentSortDirection === 'asc') {\n direction = 'desc';\n }\n }\n const offset = (this.currentPage - 1) * this.currentPageSize;\n const limit = this.currentPageSize;\n this.currentSortColumn = field;\n this.currentSortDirection = direction;\n\n this.clearSelectedRows();\n\n if (this.isServerMode) {\n this.changeForServer('sort');\n } else {\n this.filterRows();\n this.sortChange.emit({ offset, limit, field, direction });\n }\n }\n\n // checkboax\n @ViewChild('header1') header1: any;\n @ViewChild('header2') header2: any;\n checkboxChange() {\n this.checkIfAllSelected();\n const rows = this.getSelectedRows();\n this.rowSelect.emit(rows);\n }\n\n selectAll(checked: any, isAll = false) {\n this.filterItems.map((d: any) => (d.selected = checked));\n if (isAll) {\n this.checkboxChange();\n } else {\n this.checkIfAllSelected();\n }\n }\n checkIfAllSelected() {\n const cnt = this.filterItems.filter((d) => d.selected);\n this.selectedAll = cnt.length && cnt.length === this.filterItems.length;\n\n setTimeout(() => {\n this.header1?.checkboxChange();\n if (this.cloneHeaderInFooter) {\n this.header2?.checkboxChange();\n }\n });\n }\n\n // columns filter\n filterChangeMethod() {\n this.currentPage = 1;\n this.clearSelectedRows();\n\n if (this.isServerMode) {\n this.changeForServer('filter', true);\n } else {\n this.filterRows();\n this.filterChange.emit(this.columns);\n }\n }\n\n // search\n changeSearch() {\n this.currentPage = 1;\n this.clearSelectedRows();\n\n if (this.isServerMode) {\n this.changeForServer('search', true);\n } else {\n this.filterRows();\n this.searchChange.emit(this.currentSearch);\n }\n }\n\n cellValue(item: any, field: string = '') {\n return field?.split('.').reduce((obj, key) => obj?.[key], item);\n }\n\n dateFormat(date: any) {\n try {\n if (!date) {\n return '';\n }\n const dt = new Date(date);\n const day = dt.getDate();\n const month = dt.getMonth() + 1;\n const year = dt.getFullYear();\n\n return year + '-' + (month > 9 ? month : '0' + month) + '-' + (day > 9 ? day : '0' + day);\n } catch {}\n return '';\n }\n\n //row click\n timer: any = null;\n delay: number = 230;\n onRowClick(item: any, index: number) {\n clearTimeout(this.timer);\n this.timer = setTimeout(() => {\n if (this.selectRowOnClick) {\n if (this.isRowSelected(index)) {\n this.unselectRow(index);\n } else {\n this.selectRow(index);\n }\n\n this.checkboxChange();\n }\n this.rowClick.emit(item);\n }, this.delay);\n }\n\n onRowDoubleClick(item: any) {\n clearTimeout(this.timer);\n this.rowDBClick.emit(item);\n }\n\n // emit change event for server side pagination\n changeForServer(changeType: string, isResetPage = false) {\n if (this.isServerMode) {\n if (changeType === 'page') {\n this.setPager();\n }\n\n this.setDefaultCondition();\n\n const res = {\n current_page: Number(isResetPage ? 1 : this.currentPage),\n pagesize: Number(this.currentPageSize),\n offset: Number((this.currentPage - 1) * <number>this.currentPageSize),\n sort_column: this.currentSortColumn,\n sort_direction: this.currentSortDirection,\n search: this.currentSearch,\n column_filters: this.columns,\n change_type: changeType,\n };\n this.changeServer.emit(res);\n }\n }\n\n // set default conditions when values exists and condition empty\n setDefaultCondition() {\n for (let i = 0; i < this.columns.length; i++) {\n let d = this.columns[i];\n\n if (d.filter && ((d.value !== undefined && d.value !== null && d.value !== '') || d.condition === 'is_null' || d.condition === 'is_not_null')) {\n if (d.type === 'string' && d.value && !d.condition) {\n d.condition = 'contain';\n }\n if (d.type === 'number' && d.value && !d.condition) {\n d.condition = 'equal';\n }\n if (d.type === 'date' && d.value && !d.condition) {\n d.condition = 'equal';\n }\n }\n }\n }\n\n // methods\n reset() {\n this.columns = this.noReact(this.oldColumns);\n this.currentSearch = '';\n this.currentPage = 1;\n this.currentPageSize = this.oldPageSize;\n this.currentSortColumn = this.oldSortColumn;\n this.currentSortDirection = this.oldSortDirection;\n\n this.clearSelectedRows();\n\n if (this.isServerMode) {\n this.changeForServer('reset', true);\n } else {\n this.filterRows();\n }\n }\n getSelectedRows() {\n return this.filterItems.filter((d) => d.selected);\n }\n getColumnFilters() {\n return this.columns;\n }\n clearSelectedRows() {\n if (this.filterItems) {\n this.selectAll(false);\n }\n }\n selectRow(index: number) {\n if (!this.isRowSelected(index)) {\n const rows = this.filterItems.find((d: any, i: number) => i === index);\n if (rows) {\n rows.selected = true;\n }\n }\n }\n unselectRow(index: number) {\n if (this.isRowSelected(index)) {\n const rows = this.filterItems.find((d: any, i: number) => i === index);\n rows.selected = false;\n }\n }\n isRowSelected(index: number): boolean {\n const rows = this.filterItems.find((d: any, i: number) => i === index);\n if (rows) {\n return rows.selected;\n }\n return false;\n }\n\n // trackby\n trackFilterItems(index: number, item: any) {\n return this.uniqueKey ? item[this.uniqueKey] : (this.currentPage - 1) * this.pageSize + index;\n }\n\n // slots\n @ContentChildren(SlotDirective) slots: QueryList<SlotDirective>;\n @ViewChild('defaultTemplate', { static: true }) defaultTemplate: TemplateRef<any>;\n slotsMap: Map<string, TemplateRef<any>> = new Map<string, TemplateRef<any>>();\n\n ngAfterContentInit() {\n this.slots.forEach((template) => {\n const fieldName = template.fieldName;\n if (fieldName) {\n this.slotsMap.set(fieldName, template.templateRef);\n }\n });\n }\n hasSlot(fieldName: string = ''): boolean {\n return this.slotsMap.has(fieldName);\n }\n getSlot(fieldName: string = ''): TemplateRef<any> {\n return this.slotsMap.get(fieldName) || this.defaultTemplate;\n }\n\n // Sanitize and trust the HTML content\n sanitizeHtml(html: string): SafeHtml {\n return this.sanitizer.bypassSecurityTrustHtml(html);\n }\n\n noReact(value: any) {\n return JSON.parse(JSON.stringify(value));\n }\n\n getRange(size: number): number[] {\n return Array.from({ length: size }, (_, index) => index + 1);\n }\n}\n","<div class=\"bh-datatable bh-antialiased bh-relative bh-text-black bh-text-sm bh-font-normal\">\n <div class=\"bh-table-responsive\" [ngClass]=\"{'bh-min-h-[300px]': currentLoader }\" [style]=\"{ height: stickyHeader && height }\">\n <table [ngClass]=\"[skin]\">\n <thead [ngClass]=\"{ 'bh-sticky bh-top-0 bh-z-10': stickyHeader }\">\n <column-header\n #header1\n [all]=\"getProps\"\n [checkAll]=\"selectedAll\"\n (selectAll)=\"selectAll($event, true)\"\n (sortChange)=\"sortChangeMethod($event)\"\n (filterChange)=\"filterChangeMethod()\"\n ></column-header>\n </thead>\n\n <tbody>\n <ng-container *ngIf=\"filterRowCount\">\n <tr\n *ngFor=\"let item of filterItems; let i = index; trackBy:trackFilterItems.bind(this)\"\n [ngClass]=\"[(isFunction(rowClass) ? rowClass(item) : rowClass), selectRowOnClick? 'bh-cursor-pointer':'']\"\n (click)=\"onRowClick(item, i)\"\n (dblclick)=\"onRowDoubleClick(item)\"\n >\n <td *ngIf=\"hasCheckbox\" [ngClass]=\"{'bh-sticky bh-left-0 bh-bg-blue-light': stickyFirstColumn}\">\n <div class=\"bh-checkbox\" (click)=\"$event.stopPropagation();\">\n <input type=\"checkbox\" value=\"{{item[uniqueKey] ? item[uniqueKey] : i}}\" [(ngModel)]=\"item.selected\" (change)=\"checkboxChange();\" />\n <div>\n <icon-check class=\"check\"></icon-check>\n </div>\n </div>\n </td>\n <ng-container *ngFor=\"let col of columns; let j = index\">\n <td\n *ngIf=\"!col.hide\"\n [ngClass]=\"[\n (isFunction(cellClass) ? cellClass(item) : cellClass),\n j === 0 && stickyFirstColumn ? 'bh-sticky bh-left-0 bh-bg-blue-light' : '',\n hasCheckbox && j === 0 && stickyFirstColumn ? 'bh-left-[52px]' : '',\n col.cellClass ? col.cellClass : ''\n ]\"\n >\n <ng-container *ngIf=\"hasSlot(col.field)\">\n <ng-container [ngTemplateOutlet]=\"getSlot(col.field)\" [ngTemplateOutletContext]=\"{ data: item }\"></ng-container>\n </ng-container>\n <div *ngIf=\"!hasSlot(col.field) && col.cellRenderer\" [innerHTML]=\"sanitizeHtml(col.cellRenderer(item))\"></div>\n <ng-container *ngIf=\"!hasSlot(col.field) && !col.cellRenderer\"> {{ cellValue(item, col.field) }} </ng-container>\n </td>\n </ng-container>\n </tr>\n <!-- Footer row -->\n <tr *ngIf=\"showFooterRow\" class=\"footer-row\">\n <td *ngIf=\"hasCheckbox\"></td>\n <ng-container *ngFor=\"let col of columns; let j = index\">\n <td *ngIf=\"!col.hide\">\n <ng-container *ngIf=\"hasSlot(col.field + '_footer')\">\n <ng-container [ngTemplateOutlet]=\"getSlot(col.field + '_footer')\" [ngTemplateOutletContext]=\"{ data: col }\"></ng-container>\n </ng-container>\n </td>\n </ng-container>\n </tr>\n </ng-container>\n <tr *ngIf=\"!filterRowCount && !currentLoader\">\n <td [attr.colspan]=\"columns.length + 1\">{{ noDataContent }}</td>\n </tr>\n\n <ng-container *ngIf=\"!filterRowCount && current