@pascalhonegger/ng-datatable
Version:
DataTable component for Angular framework
436 lines (427 loc) • 22.8 kB
JavaScript
import * as i0 from '@angular/core';
import { input, model, computed, effect, untracked, Directive, inject, signal, DestroyRef, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
import { Subject } from 'rxjs';
class DataTable {
/** Array of data to display in table */
inputData = input.required({ ...(ngDevMode ? { debugName: "inputData" } : {}), alias: 'mfData',
transform: (i) => i ?? [] });
/** Sort by parameter */
sortBy = model('', { ...(ngDevMode ? { debugName: "sortBy" } : {}), alias: 'mfSortBy' });
/** Sort order parameter (either `asc` or `desc`, default: `asc`) */
sortOrder = model('asc', { ...(ngDevMode ? { debugName: "sortOrder" } : {}), alias: 'mfSortOrder' });
/** Number of rows should be displayed on page (default: `1000`) */
rowsOnPage = model(1000, { ...(ngDevMode ? { debugName: "rowsOnPage" } : {}), alias: 'mfRowsOnPage' });
/** Page number (default: `1`) */
activePage = model(1, { ...(ngDevMode ? { debugName: "activePage" } : {}), alias: 'mfActivePage' });
inputDataLength = computed(() => this.inputData().length, ...(ngDevMode ? [{ debugName: "inputDataLength" }] : []));
data = computed(() => {
const offset = (this.activePage() - 1) * this.rowsOnPage();
return [...this.inputData()]
.sort(this.sorter(this.sortBy(), this.sortOrder()))
.slice(offset, offset + this.rowsOnPage());
}, ...(ngDevMode ? [{ debugName: "data" }] : []));
onSortChange = new Subject();
onPageChange = new Subject();
constructor() {
// Events which were published based on the old API, could probably be deleted at some point
effect(() => {
const sortBy = this.sortBy();
const sortOrder = this.sortOrder();
if (sortBy) {
this.onSortChange.next({ sortBy: sortBy, sortOrder: sortOrder });
}
});
effect(() => {
this.setPage(untracked(this.activePage), this.rowsOnPage());
});
effect(() => {
const inputDataLength = this.inputDataLength();
const rowsOnPage = this.rowsOnPage();
const activePage = untracked(this.activePage);
const lastPage = Math.ceil(inputDataLength / rowsOnPage);
const newActivePage = (lastPage < activePage ? lastPage : activePage) || 1;
this.activePage.set(newActivePage);
});
effect(() => {
this.onPageChange.next({
activePage: this.activePage(),
rowsOnPage: this.rowsOnPage(),
dataLength: this.inputDataLength(),
});
});
}
getSort() {
return { sortBy: this.sortBy(), sortOrder: this.sortOrder() };
}
setSort(sortBy, sortOrder) {
this.sortBy.set(sortBy);
this.sortOrder.set(['asc', 'desc'].includes(sortOrder) ? sortOrder : 'asc');
}
getPage() {
return {
activePage: this.activePage(),
rowsOnPage: this.rowsOnPage(),
dataLength: this.inputDataLength(),
};
}
setPage(activePage, rowsOnPage) {
if (this.rowsOnPage() !== rowsOnPage || this.activePage() !== activePage) {
this.rowsOnPage.set(rowsOnPage);
this.activePage.set(this.activePage() !== activePage
? activePage
: this.calculateNewActivePage(this.rowsOnPage(), rowsOnPage));
}
}
calculateNewActivePage(previousRowsOnPage, currentRowsOnPage) {
const firstRowOnPage = (this.activePage() - 1) * previousRowsOnPage + 1;
const newActivePage = Math.ceil(firstRowOnPage / currentRowsOnPage);
return newActivePage;
}
caseInsensitiveIteratee(sortBy) {
return (row) => {
let value = row;
if (typeof sortBy === 'string' || sortBy instanceof String) {
for (const sortByProperty of sortBy.split('.')) {
if (value) {
value = value[sortByProperty];
}
}
}
else if (typeof sortBy === 'function') {
value = sortBy(value);
}
if ((value && typeof value === 'string') || value instanceof String) {
return value.toLowerCase();
}
return value;
};
}
compare(left, right) {
if (left === right) {
return 0;
}
if (left == null && right != null) {
return -1;
}
if (right == null) {
return 1;
}
return left > right ? 1 : -1;
}
sorter(sortBy, sortOrder) {
const order = sortOrder === 'desc' ? -1 : 1;
if (Array.isArray(sortBy)) {
const iteratees = sortBy.map((entry) => this.caseInsensitiveIteratee(entry));
return (left, right) => {
for (const iteratee of iteratees) {
const comparison = this.compare(iteratee(left), iteratee(right)) * order;
if (comparison !== 0) {
return comparison;
}
}
return 0;
};
}
else {
const iteratee = this.caseInsensitiveIteratee(sortBy);
return (left, right) => this.compare(iteratee(left), iteratee(right)) * order;
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataTable, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: DataTable, isStandalone: true, selector: "table[mfData]", inputs: { inputData: { classPropertyName: "inputData", publicName: "mfData", isSignal: true, isRequired: true, transformFunction: null }, sortBy: { classPropertyName: "sortBy", publicName: "mfSortBy", isSignal: true, isRequired: false, transformFunction: null }, sortOrder: { classPropertyName: "sortOrder", publicName: "mfSortOrder", isSignal: true, isRequired: false, transformFunction: null }, rowsOnPage: { classPropertyName: "rowsOnPage", publicName: "mfRowsOnPage", isSignal: true, isRequired: false, transformFunction: null }, activePage: { classPropertyName: "activePage", publicName: "mfActivePage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortBy: "mfSortByChange", sortOrder: "mfSortOrderChange", rowsOnPage: "mfRowsOnPageChange", activePage: "mfActivePageChange" }, exportAs: ["mfDataTable"], ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataTable, decorators: [{
type: Directive,
args: [{
selector: 'table[mfData]',
exportAs: 'mfDataTable',
}]
}], ctorParameters: () => [], propDecorators: { inputData: [{ type: i0.Input, args: [{ isSignal: true, alias: "mfData", required: true }] }], sortBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "mfSortBy", required: false }] }, { type: i0.Output, args: ["mfSortByChange"] }], sortOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "mfSortOrder", required: false }] }, { type: i0.Output, args: ["mfSortOrderChange"] }], rowsOnPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "mfRowsOnPage", required: false }] }, { type: i0.Output, args: ["mfRowsOnPageChange"] }], activePage: [{ type: i0.Input, args: [{ isSignal: true, alias: "mfActivePage", required: false }] }, { type: i0.Output, args: ["mfActivePageChange"] }] } });
class Paginator {
injectMfTable = inject(DataTable, { optional: true });
/** explicitly specify reference data table, by default the parent `mfData` is injected */
inputMfTable = input(undefined, { ...(ngDevMode ? { debugName: "inputMfTable" } : {}), alias: 'mfTable' });
mfTable = computed(() => this.inputMfTable() ?? this.injectMfTable, ...(ngDevMode ? [{ debugName: "mfTable" }] : []));
activePage = signal(0, ...(ngDevMode ? [{ debugName: "activePage" }] : []));
rowsOnPage = signal(0, ...(ngDevMode ? [{ debugName: "rowsOnPage" }] : []));
dataLength = signal(0, ...(ngDevMode ? [{ debugName: "dataLength" }] : []));
lastPage = computed(() => {
const rowsOnPage = this.rowsOnPage();
const dataLength = this.dataLength();
return rowsOnPage === 0 ? 0 : Math.ceil(dataLength / rowsOnPage);
}, ...(ngDevMode ? [{ debugName: "lastPage" }] : []));
constructor() {
let currentSubscription = undefined;
effect(() => {
const currentTable = this.mfTable();
this.onPageChangeSubscriber(currentTable.getPage());
currentSubscription?.unsubscribe();
currentSubscription = currentTable.onPageChange.subscribe(this.onPageChangeSubscriber);
});
inject(DestroyRef).onDestroy(() => {
currentSubscription?.unsubscribe();
});
}
setPage(pageNumber) {
this.mfTable().setPage(pageNumber, this.rowsOnPage());
}
setRowsOnPage(rowsOnPage) {
this.mfTable().setPage(this.activePage(), rowsOnPage);
}
onPageChangeSubscriber = (event) => {
this.activePage.set(event.activePage);
this.rowsOnPage.set(event.rowsOnPage);
this.dataLength.set(event.dataLength);
};
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Paginator, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: Paginator, isStandalone: true, selector: "mfPaginator", inputs: { inputMfTable: { classPropertyName: "inputMfTable", publicName: "mfTable", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Paginator, decorators: [{
type: Component,
args: [{
selector: 'mfPaginator',
template: `<ng-content />`,
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}], ctorParameters: () => [], propDecorators: { inputMfTable: [{ type: i0.Input, args: [{ isSignal: true, alias: "mfTable", required: false }] }] } });
class BootstrapPaginator {
/** Specify values for buttons to change number of diplayed rows, e.g. [5, 10, 15] */
rowsOnPageSet = input.required(...(ngDevMode ? [{ debugName: "rowsOnPageSet" }] : []));
/** explicitly specify reference data table, by default the parent `mfData` is injected */
mfTable = input(...(ngDevMode ? [undefined, { debugName: "mfTable" }] : []));
minRowsOnPage = computed(() => this.rowsOnPageSet().reduce((previous, current) => (current < previous ? current : previous), 0), ...(ngDevMode ? [{ debugName: "minRowsOnPage" }] : []));
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: BootstrapPaginator, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: BootstrapPaginator, isStandalone: true, selector: "mfBootstrapPaginator", inputs: { rowsOnPageSet: { classPropertyName: "rowsOnPageSet", publicName: "rowsOnPageSet", isSignal: true, isRequired: true, transformFunction: null }, mfTable: { classPropertyName: "mfTable", publicName: "mfTable", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
<mfPaginator #p [mfTable]="mfTable()">
(p.dataLength() > p.rowsOnPage()) {
<ul class="pagination float-start">
<li class="page-item" [class.disabled]="p.activePage() <= 1" (click)="p.setPage(1)">
<a class="page-link" aria-label="Previous"><span aria-hidden="true">«</span></a>
</li>
(p.activePage() > 4 && p.activePage() + 1 > p.lastPage()) {
<li class="page-item" (click)="p.setPage(p.activePage() - 4)">
<a class="page-link">{{ p.activePage() - 4 }}</a>
</li>
}
(p.activePage() > 3 && p.activePage() + 2 > p.lastPage()) {
<li class="page-item" (click)="p.setPage(p.activePage() - 3)">
<a class="page-link">{{ p.activePage() - 3 }}</a>
</li>
}
(p.activePage() > 2) {
<li class="page-item" (click)="p.setPage(p.activePage() - 2)">
<a class="page-link">{{ p.activePage() - 2 }}</a>
</li>
}
(p.activePage() > 1) {
<li class="page-item" (click)="p.setPage(p.activePage() - 1)">
<a class="page-link">{{ p.activePage() - 1 }}</a>
</li>
}
<li class="page-item active">
<a class="page-link">{{ p.activePage() }}</a>
</li>
(p.activePage() + 1 <= p.lastPage()) {
<li class="page-item" (click)="p.setPage(p.activePage() + 1)">
<a class="page-link">{{ p.activePage() + 1 }}</a>
</li>
}
(p.activePage() + 2 <= p.lastPage()) {
<li class="page-item" (click)="p.setPage(p.activePage() + 2)">
<a class="page-link">{{ p.activePage() + 2 }}</a>
</li>
}
(p.activePage() + 3 <= p.lastPage() && p.activePage() < 3) {
<li class="page-item" (click)="p.setPage(p.activePage() + 3)">
<a class="page-link">{{ p.activePage() + 3 }}</a>
</li>
}
(p.activePage() + 4 <= p.lastPage() && p.activePage() < 2) {
<li class="page-item" (click)="p.setPage(p.activePage() + 4)">
<a class="page-link">{{ p.activePage() + 4 }}</a>
</li>
}
<li
class="page-item"
[class.disabled]="p.activePage() >= p.lastPage()"
(click)="p.setPage(p.lastPage())"
>
<a class="page-link" aria-label="Next"><span aria-hidden="true">»</span></a>
</li>
</ul>
}
(p.dataLength() > minRowsOnPage()) {
<ul class="pagination float-end">
(rows of rowsOnPageSet(); track rows) {
<li
class="page-item"
[class.active]="p.rowsOnPage() === rows"
(click)="p.setRowsOnPage(rows)"
>
<a class="page-link">{{ rows }}</a>
</li>
}
</ul>
}
</mfPaginator>
`, isInline: true, styles: [".page-link{cursor:pointer}\n"], dependencies: [{ kind: "component", type: Paginator, selector: "mfPaginator", inputs: ["mfTable"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: BootstrapPaginator, decorators: [{
type: Component,
args: [{ selector: 'mfBootstrapPaginator', template: `
<mfPaginator #p [mfTable]="mfTable()">
(p.dataLength() > p.rowsOnPage()) {
<ul class="pagination float-start">
<li class="page-item" [class.disabled]="p.activePage() <= 1" (click)="p.setPage(1)">
<a class="page-link" aria-label="Previous"><span aria-hidden="true">«</span></a>
</li>
(p.activePage() > 4 && p.activePage() + 1 > p.lastPage()) {
<li class="page-item" (click)="p.setPage(p.activePage() - 4)">
<a class="page-link">{{ p.activePage() - 4 }}</a>
</li>
}
(p.activePage() > 3 && p.activePage() + 2 > p.lastPage()) {
<li class="page-item" (click)="p.setPage(p.activePage() - 3)">
<a class="page-link">{{ p.activePage() - 3 }}</a>
</li>
}
(p.activePage() > 2) {
<li class="page-item" (click)="p.setPage(p.activePage() - 2)">
<a class="page-link">{{ p.activePage() - 2 }}</a>
</li>
}
(p.activePage() > 1) {
<li class="page-item" (click)="p.setPage(p.activePage() - 1)">
<a class="page-link">{{ p.activePage() - 1 }}</a>
</li>
}
<li class="page-item active">
<a class="page-link">{{ p.activePage() }}</a>
</li>
(p.activePage() + 1 <= p.lastPage()) {
<li class="page-item" (click)="p.setPage(p.activePage() + 1)">
<a class="page-link">{{ p.activePage() + 1 }}</a>
</li>
}
(p.activePage() + 2 <= p.lastPage()) {
<li class="page-item" (click)="p.setPage(p.activePage() + 2)">
<a class="page-link">{{ p.activePage() + 2 }}</a>
</li>
}
(p.activePage() + 3 <= p.lastPage() && p.activePage() < 3) {
<li class="page-item" (click)="p.setPage(p.activePage() + 3)">
<a class="page-link">{{ p.activePage() + 3 }}</a>
</li>
}
(p.activePage() + 4 <= p.lastPage() && p.activePage() < 2) {
<li class="page-item" (click)="p.setPage(p.activePage() + 4)">
<a class="page-link">{{ p.activePage() + 4 }}</a>
</li>
}
<li
class="page-item"
[class.disabled]="p.activePage() >= p.lastPage()"
(click)="p.setPage(p.lastPage())"
>
<a class="page-link" aria-label="Next"><span aria-hidden="true">»</span></a>
</li>
</ul>
}
(p.dataLength() > minRowsOnPage()) {
<ul class="pagination float-end">
(rows of rowsOnPageSet(); track rows) {
<li
class="page-item"
[class.active]="p.rowsOnPage() === rows"
(click)="p.setRowsOnPage(rows)"
>
<a class="page-link">{{ rows }}</a>
</li>
}
</ul>
}
</mfPaginator>
`, imports: [Paginator], changeDetection: ChangeDetectionStrategy.OnPush, styles: [".page-link{cursor:pointer}\n"] }]
}], propDecorators: { rowsOnPageSet: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsOnPageSet", required: true }] }], mfTable: [{ type: i0.Input, args: [{ isSignal: true, alias: "mfTable", required: false }] }] } });
class DefaultSorter {
mfTable = inject((DataTable));
/** Specify how to sort data (argument for lodash function [\_.sortBy ](https://lodash.com/docs#sortBy)) */
sortBy = input.required({ ...(ngDevMode ? { debugName: "sortBy" } : {}), alias: 'by' });
isSortedByMeAsc = computed(() => {
const tableSortBy = this.mfTable.sortBy();
const tableSortOrder = this.mfTable.sortOrder();
const mySort = this.sortBy();
return tableSortBy == mySort && tableSortOrder === 'asc';
}, ...(ngDevMode ? [{ debugName: "isSortedByMeAsc" }] : []));
isSortedByMeDesc = computed(() => {
const tableSortBy = this.mfTable.sortBy();
const tableSortOrder = this.mfTable.sortOrder();
const mySort = this.sortBy();
return tableSortBy == mySort && tableSortOrder === 'desc';
}, ...(ngDevMode ? [{ debugName: "isSortedByMeDesc" }] : []));
sort() {
if (this.isSortedByMeAsc()) {
this.mfTable.setSort(this.sortBy(), 'desc');
}
else {
this.mfTable.setSort(this.sortBy(), 'asc');
}
return false;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultSorter, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DefaultSorter, isStandalone: true, selector: "mfDefaultSorter", inputs: { sortBy: { classPropertyName: "sortBy", publicName: "by", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: ` <a
(click)="sort()"
(keydown.enter)="sort()"
(keydown.space)="sort()"
class="text-nowrap text-decoration-none"
tabindex="0"
>
<ng-content />
(isSortedByMeAsc()) {
<span aria-hidden="true" aria-label="asc">▲</span>
} if (isSortedByMeDesc()) {
<span aria-hidden="true" aria-label="desc">▼</span>
}
</a>`, isInline: true, styles: ["a{cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultSorter, decorators: [{
type: Component,
args: [{ selector: 'mfDefaultSorter', template: ` <a
(click)="sort()"
(keydown.enter)="sort()"
(keydown.space)="sort()"
class="text-nowrap text-decoration-none"
tabindex="0"
>
<ng-content />
(isSortedByMeAsc()) {
<span aria-hidden="true" aria-label="asc">▲</span>
} if (isSortedByMeDesc()) {
<span aria-hidden="true" aria-label="desc">▼</span>
}
</a>`, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["a{cursor:pointer}\n"] }]
}], propDecorators: { sortBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "by", required: true }] }] } });
/**
* Optional module which exports all components
* @deprecated Should be replaced with component imports (DataTable, DefaultSorter, Paginator, BootstrapPaginator)
*/
class DataTableModule {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataTableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: DataTableModule, imports: [DataTable, DefaultSorter, Paginator, BootstrapPaginator], exports: [DataTable, DefaultSorter, Paginator, BootstrapPaginator] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataTableModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataTableModule, decorators: [{
type: NgModule,
args: [{
imports: [DataTable, DefaultSorter, Paginator, BootstrapPaginator],
exports: [DataTable, DefaultSorter, Paginator, BootstrapPaginator],
}]
}] });
/*
* Public API Surface of ng-datatable
*/
/**
* Generated bundle index. Do not edit.
*/
export { BootstrapPaginator, DataTable, DataTableModule, DefaultSorter, Paginator };
//# sourceMappingURL=pascalhonegger-ng-datatable.mjs.map