diff --git a/projects/components/table/src/data/table-data-source.ts b/projects/components/table/src/data/table-data-source.ts index 9baefa33..4772e2c3 100644 --- a/projects/components/table/src/data/table-data-source.ts +++ b/projects/components/table/src/data/table-data-source.ts @@ -65,7 +65,7 @@ export class PsTableDataSource extends DataSource { public sortColumn: string = null; /** The sort direction. Defaulted to asc. */ - public sortDirection: 'asc' | 'desc' = 'asc'; + public sortDirection: 'asc' | 'desc' | null = 'asc'; /** The zero-based page index of the displayed list of items. Defaulted to 0. */ public pageIndex = 0; @@ -290,7 +290,7 @@ export class PsTableDataSource extends DataSource { currentPage: this.pageIndex, searchText: this.filter, sortColumn: this.sortColumn, - sortDirection: this.sortDirection, + sortDirection: this.sortDirection || null, }; if (extended) { (data as IExtendedPsTableUpdateDataInfo).triggerData = this._lastLoadTriggerData; @@ -433,7 +433,7 @@ export class PsTableDataSource extends DataSource { return this.sortData(data.slice(), { sortColumn: this.sortColumn, - sortDirection: this.sortDirection, + sortDirection: this.sortDirection || null, }); } diff --git a/projects/components/table/src/services/table-settings.service.ts b/projects/components/table/src/services/table-settings.service.ts index f2989a48..0e1093c5 100644 --- a/projects/components/table/src/services/table-settings.service.ts +++ b/projects/components/table/src/services/table-settings.service.ts @@ -4,7 +4,7 @@ import { EMPTY, Observable, of } from 'rxjs'; export interface IPsTableSetting { columnBlacklist: string[]; sortColumn: string; - sortDirection: 'asc' | 'desc'; + sortDirection: 'asc' | 'desc' | null; pageSize: number; } diff --git a/projects/components/table/src/subcomponents/table-data.component.html b/projects/components/table/src/subcomponents/table-data.component.html index 18850548..b9f1d829 100644 --- a/projects/components/table/src/subcomponents/table-data.component.html +++ b/projects/components/table/src/subcomponents/table-data.component.html @@ -1,4 +1,14 @@ - +
@@ -23,7 +33,14 @@ - + {{ columnDef.header }} diff --git a/projects/components/table/src/subcomponents/table-data.component.ts b/projects/components/table/src/subcomponents/table-data.component.ts index ed2c1a93..9b266aad 100644 --- a/projects/components/table/src/subcomponents/table-data.component.ts +++ b/projects/components/table/src/subcomponents/table-data.component.ts @@ -14,6 +14,7 @@ import { IPsTableIntlTexts } from '@prosoft/components/core'; import { PsTableDataSource } from '../data/table-data-source'; import { PsTableColumnDirective, PsTableRowDetailDirective } from '../directives/table.directives'; import { Subscription } from 'rxjs'; +import { Sort } from '@angular/material/sort'; @Component({ selector: 'ps-table-data', @@ -32,6 +33,8 @@ export class PsTableDataComponent implements OnChanges { @Input() public refreshable: boolean; @Input() public settingsEnabled: boolean; @Input() public displayedColumns: string[]; + @Input() public sortColumn: string; + @Input() public sortDirection: 'asc' | 'desc' | null; /** * @deprecated Please use the action definition in PsTableDataSource */ @@ -44,6 +47,7 @@ export class PsTableDataComponent implements OnChanges { @Output() public showSettingsClicked = new EventEmitter(); @Output() public refreshDataClicked = new EventEmitter(); + @Output() public sortChanged = new EventEmitter(); private _dataSourceChangesSub = Subscription.EMPTY; @@ -66,6 +70,10 @@ export class PsTableDataComponent implements OnChanges { this.refreshDataClicked.emit(); } + public onSortChanged(sort: Sort) { + this.sortChanged.emit(sort); + } + public toggleRowDetail(item: { [key: string]: any }) { this.rowDetail.toggle(item); this.cd.markForCheck(); diff --git a/projects/components/table/src/subcomponents/table-header.component.ts b/projects/components/table/src/subcomponents/table-header.component.ts index 66d0564d..ad01037f 100644 --- a/projects/components/table/src/subcomponents/table-header.component.ts +++ b/projects/components/table/src/subcomponents/table-header.component.ts @@ -8,6 +8,7 @@ import { ViewEncapsulation, HostBinding, } from '@angular/core'; +import { Sort } from '@angular/material/sort'; import { IPsTableIntlTexts } from '@prosoft/components/core'; import { IPsTableSortDefinition } from '../models'; @@ -83,12 +84,12 @@ export class PsTableHeaderComponent { @Input() public selectedRows: any[]; @Input() public showSorting: boolean; @Input() public sortColumn: string; - @Input() public sortDirection: 'asc' | 'desc'; + @Input() public sortDirection: 'asc' | 'desc' | null; @Input() public sortDefinitions: IPsTableSortDefinition[] = []; @Input() public filterable: boolean; @Input() public searchText: string; - @Output() public sortChanged = new EventEmitter<{ sortColumn: string; sortDirection: 'asc' | 'desc' }>(); + @Output() public sortChanged = new EventEmitter(); @Output() public searchChanged = new EventEmitter(); @HostBinding('style.padding-top') public get paddingTop() { diff --git a/projects/components/table/src/subcomponents/table-settings.component.spec.ts b/projects/components/table/src/subcomponents/table-settings.component.spec.ts index d69ba768..1333e80e 100644 --- a/projects/components/table/src/subcomponents/table-settings.component.spec.ts +++ b/projects/components/table/src/subcomponents/table-settings.component.spec.ts @@ -17,6 +17,7 @@ import { PsTableSettingsComponent } from './table-settings.component'; import { PsTableSortComponent } from './table-sort.component'; import { By } from '@angular/platform-browser'; import { delay } from 'rxjs/operators'; +import { Sort } from '@angular/material/sort'; @Component({ selector: 'ps-test-component', @@ -202,8 +203,8 @@ describe('PsTableSettingsComponent', () => { sortDirection: sortDirection, } as Partial) as any; } - function createColumnDef(sortColumn: string, sortDirection: 'asc' | 'desc') { - return { sortColumn: sortColumn, sortDirection: sortDirection }; + function createColumnDef(sortColumn: string, sortDirection: 'asc' | 'desc'): Sort { + return { active: sortColumn, direction: sortDirection }; } it('should remove sort column from blacklist', fakeAsync(() => { diff --git a/projects/components/table/src/subcomponents/table-settings.component.ts b/projects/components/table/src/subcomponents/table-settings.component.ts index 508cfacd..5b38aadf 100644 --- a/projects/components/table/src/subcomponents/table-settings.component.ts +++ b/projects/components/table/src/subcomponents/table-settings.component.ts @@ -1,5 +1,6 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, TemplateRef } from '@angular/core'; import { MatCheckboxChange } from '@angular/material/checkbox'; +import { Sort } from '@angular/material/sort'; import { IPsTableIntlTexts } from '@prosoft/components/core'; import { Observable, Subscription } from 'rxjs'; import { first, map } from 'rxjs/operators'; @@ -63,12 +64,12 @@ export class PsTableSettingsComponent implements OnInit { return !settings.columnBlacklist.some((x) => x === columnDef.property); } - public onSortChanged(event: { sortColumn: string; sortDirection: 'asc' | 'desc' }, settings: IPsTableSetting) { - if (settings.sortColumn !== event.sortColumn) { - settings.sortColumn = event.sortColumn; - settings.columnBlacklist = settings.columnBlacklist.filter((x) => x !== event.sortColumn); + public onSortChanged(event: Sort, settings: IPsTableSetting) { + if (settings.sortColumn !== event.active) { + settings.sortColumn = event.active; + settings.columnBlacklist = settings.columnBlacklist.filter((x) => x !== event.active); } - settings.sortDirection = event.sortDirection; + settings.sortDirection = event.direction || null; } public onColumnVisibilityChange(event: MatCheckboxChange, settings: IPsTableSetting, columnDef: PsTableColumnDirective) { diff --git a/projects/components/table/src/subcomponents/table-sort.component.spec.ts b/projects/components/table/src/subcomponents/table-sort.component.spec.ts index 80f3065c..30b3219a 100644 --- a/projects/components/table/src/subcomponents/table-sort.component.spec.ts +++ b/projects/components/table/src/subcomponents/table-sort.component.spec.ts @@ -5,6 +5,7 @@ import { MatButton, MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatSelectModule } from '@angular/material/select'; +import { Sort } from '@angular/material/sort'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { IPsTableSortDefinition } from '../models'; @@ -35,7 +36,7 @@ export class TestComponent { @ViewChild(PsTableSortComponent, { static: true }) tableSort: PsTableSortComponent; - public onSortChanged(_event: any) {} + public onSortChanged(_event: Sort) {} } describe('PsTableSortComponent', () => { @@ -62,14 +63,14 @@ describe('PsTableSortComponent', () => { descButton.triggerEventHandler('click', new MouseEvent('click')); expect(component.onSortChanged).toHaveBeenCalledWith({ - sortColumn: 'prop', - sortDirection: 'desc', + active: 'prop', + direction: 'desc', }); ascButton.triggerEventHandler('click', new MouseEvent('click')); expect(component.onSortChanged).toHaveBeenCalledWith({ - sortColumn: 'prop', - sortDirection: 'asc', + active: 'prop', + direction: 'asc', }); component.sortDefinitions = [ @@ -87,8 +88,8 @@ describe('PsTableSortComponent', () => { const itemNode = matOptionNodes.item(1); itemNode.dispatchEvent(new Event('click')); expect(component.onSortChanged).toHaveBeenCalledWith({ - sortColumn: 'newprop', - sortDirection: 'asc', + active: 'newprop', + direction: 'asc', }); }); diff --git a/projects/components/table/src/subcomponents/table-sort.component.ts b/projects/components/table/src/subcomponents/table-sort.component.ts index a3a43f75..ed893a59 100644 --- a/projects/components/table/src/subcomponents/table-sort.component.ts +++ b/projects/components/table/src/subcomponents/table-sort.component.ts @@ -1,91 +1,92 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; -import { MatSelectChange } from '@angular/material/select'; -import { IPsTableIntlTexts } from '@prosoft/components/core'; -import { IPsTableSortDefinition } from '../models'; - -@Component({ - selector: 'ps-table-sort', - template: ` - - {{ intl.sortLabel }} - - {{ - sortDefinition.displayName - }} - - - - - `, - styles: [ - ` - ps-table-sort { - display: grid; - grid-template-columns: 1fr min-content min-content; - } - - .ps-table-sort__dir-button { - width: 28px; - height: 28px; - line-height: 28px; - margin-top: 16px; - margin-left: 0.2em; - } - - .ps-table-sort__dir-button .mat-button-wrapper { - padding: 0; - } - - .ps-table-sort__dir-button--inactive { - background-color: transparent !important; - color: black !important; - } - `, - ], - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, -}) -export class PsTableSortComponent { - @Input() public intl: IPsTableIntlTexts; - @Input() public sortColumn: string; - @Input() public sortDirection: 'asc' | 'desc'; - @Input() public sortDefinitions: IPsTableSortDefinition[] = []; - @Output() public sortChanged = new EventEmitter<{ sortColumn: string; sortDirection: 'asc' | 'desc' }>(); - - public onSortColumnChange(event: MatSelectChange) { - if (this.sortColumn !== event.value) { - this.sortColumn = event.value; - this.emitChange(); - } - } - - public onSortSirectionChange(dir: 'asc' | 'desc') { - if (this.sortDirection !== dir) { - this.sortDirection = dir; - this.emitChange(); - } - } - - private emitChange() { - this.sortChanged.emit({ - sortColumn: this.sortColumn, - sortDirection: this.sortDirection, - }); - } -} +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; +import { MatSelectChange } from '@angular/material/select'; +import { Sort } from '@angular/material/sort'; +import { IPsTableIntlTexts } from '@prosoft/components/core'; +import { IPsTableSortDefinition } from '../models'; + +@Component({ + selector: 'ps-table-sort', + template: ` + + {{ intl.sortLabel }} + + {{ + sortDefinition.displayName + }} + + + + + `, + styles: [ + ` + ps-table-sort { + display: grid; + grid-template-columns: 1fr min-content min-content; + } + + .ps-table-sort__dir-button { + width: 28px; + height: 28px; + line-height: 28px; + margin-top: 16px; + margin-left: 0.2em; + } + + .ps-table-sort__dir-button .mat-button-wrapper { + padding: 0; + } + + .ps-table-sort__dir-button--inactive { + background-color: transparent !important; + color: black !important; + } + `, + ], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, +}) +export class PsTableSortComponent { + @Input() public intl: IPsTableIntlTexts; + @Input() public sortColumn: string; + @Input() public sortDirection: 'asc' | 'desc' | null; + @Input() public sortDefinitions: IPsTableSortDefinition[] = []; + @Output() public sortChanged = new EventEmitter(); + + public onSortColumnChange(event: MatSelectChange) { + if (this.sortColumn !== event.value) { + this.sortColumn = event.value; + this.emitChange(); + } + } + + public onSortSirectionChange(dir: 'asc' | 'desc') { + if (this.sortDirection !== dir) { + this.sortDirection = dir; + this.emitChange(); + } + } + + private emitChange() { + this.sortChanged.emit({ + active: this.sortColumn, + direction: this.sortDirection, + }); + } +} diff --git a/projects/components/table/src/table.component.html b/projects/components/table/src/table.component.html index 0f863a5d..d8fcb5cd 100644 --- a/projects/components/table/src/table.component.html +++ b/projects/components/table/src/table.component.html @@ -1,4 +1,4 @@ - +
{{ intl.noEntriesLabel }}
diff --git a/projects/components/table/src/table.component.spec.ts b/projects/components/table/src/table.component.spec.ts index 6bc23388..fbce7920 100644 --- a/projects/components/table/src/table.component.spec.ts +++ b/projects/components/table/src/table.component.spec.ts @@ -39,7 +39,9 @@ class TestSettingsService extends PsTableSettingsService { } const router: any = { - navigate: (_route: any, _options: any) => {}, + navigate: (_route: any, options: any) => { + queryParams$.next(convertToParamMap(options.queryParams)); + }, }; const queryParams$ = new BehaviorSubject(convertToParamMap({ other: 'value' })); @@ -162,14 +164,25 @@ describe('PsTableComponent', () => { const cd = { markForCheck: () => {} }; let settingsService: TestSettingsService; - function createTableInstance(): PsTableComponent { + function createTableInstance(hooks = false): PsTableComponent { settingsService = new TestSettingsService(); const table = new PsTableComponent(intlService, settingsService, null, cd, route, router, 'de'); table.tableId = 'tableid'; - table.dataSource = new PsTableDataSource(() => of([{ a: 'asdfg' }, { a: 'gasdf' }, { a: 'asdas' }, { a: '32424rw' }])); + table.dataSource = new PsTableDataSource({ + loadDataFn: () => of([{ a: 'asdfg' }, { a: 'gasdf' }, { a: 'asdas' }, { a: '32424rw' }]), + }); + if(hooks){ + table.ngOnChanges({}); + table.ngOnInit(); + table.ngAfterContentInit(); + } return table; } + beforeEach(() => { + queryParams$.next(convertToParamMap({ other: 'value' })); + }); + it('should update table state from the settings service and the query params', fakeAsync(() => { const table = createTableInstance(); settingsService.settings$.next({}); @@ -187,7 +200,7 @@ describe('PsTableComponent', () => { expect(table.pageIndex).toEqual(0); expect(table.filterText).toEqual(''); expect(table.sortColumn).toEqual(null); - expect(table.sortDirection).toEqual('asc'); + expect(table.sortDirection).toEqual(null); expect(table.displayedColumns).toEqual(['select', 'rowDetailExpander', 'prop1', 'prop2', 'options']); expect(settingsService.getStream).toHaveBeenCalledWith(table.tableId, false); @@ -313,7 +326,7 @@ describe('PsTableComponent', () => { })); it('should merge sort definitions and disable sorting on empty', fakeAsync(() => { - const table = createTableInstance(); + const table = createTableInstance(false); const customSortDef = { prop: 'custom', displayName: 'Custom' }; const notSortableColDef = new PsTableColumnDirective(); notSortableColDef.sortable = false; @@ -389,27 +402,30 @@ describe('PsTableComponent', () => { })); it('should update state when sort changes', fakeAsync(() => { - const table = createTableInstance(); - spyOn(table, 'requestUpdate'); - table.onSortChanged({ sortColumn: 'col', sortDirection: 'desc' }); + const table = createTableInstance(true); + spyOn(table, 'requestUpdate').and.callThrough(); + table.onSortChanged({ active: 'col', direction: 'desc' }); + expect((table).requestUpdate).toHaveBeenCalledTimes(1); + tick(1); expect(table.sortColumn).toEqual('col'); expect(table.sortDirection).toEqual('desc'); - expect((table).requestUpdate).toHaveBeenCalledTimes(1); })); it('should update state when filter changes', fakeAsync(() => { - const table = createTableInstance(); - spyOn(table, 'requestUpdate'); + const table = createTableInstance(true); + spyOn(table, 'requestUpdate').and.callThrough(); table.onSearchChanged('test'); - expect(table.filterText).toEqual('test'); expect((table).requestUpdate).toHaveBeenCalledTimes(1); + tick(1); + expect(table.filterText).toEqual('test'); })); it('should update state when page changes and emit output', fakeAsync(() => { - const table = createTableInstance(); + const table = createTableInstance(true); spyOn(table.page, 'emit'); - spyOn(table, 'requestUpdate'); + spyOn(table, 'requestUpdate').and.callThrough(); table.onPage({ pageIndex: 5, pageSize: 3, length: 20, previousPageIndex: 4 }); + tick(1); expect(table.pageIndex).toEqual(5); expect(table.pageSize).toEqual(3); expect((table).requestUpdate).toHaveBeenCalledTimes(1); @@ -664,16 +680,16 @@ describe('PsTableComponent', () => { spyOn(component.table, 'onSortChanged'); await sortSelect.clickOptions({ text: 'id' }); expect(component.table.onSortChanged).toHaveBeenCalledWith({ - sortColumn: 'id', - sortDirection: 'asc', + active: 'id', + direction: null, }); const sortDirectionButtons = await table.getSortDirectionButtons(); expect(sortDirectionButtons.length).toEqual(2); await sortDirectionButtons[0].click(); expect(component.table.onSortChanged).toHaveBeenCalledWith({ - sortColumn: 'id', - sortDirection: 'desc', + active: 'id', + direction: 'desc', }); }); diff --git a/projects/components/table/src/table.component.ts b/projects/components/table/src/table.component.ts index d96820d4..989d40c1 100644 --- a/projects/components/table/src/table.component.ts +++ b/projects/components/table/src/table.component.ts @@ -39,6 +39,7 @@ import { import { PsTableStateManager, PsTableUrlStateManager } from './helper/state-manager'; import { IPsTableSortDefinition, IPsTableUpdateDataInfo } from './models'; import { IPsTableSetting, PsTableSettingsService } from './services/table-settings.service'; +import { Sort } from '@angular/material/sort'; @Component({ selector: 'ps-table', @@ -82,6 +83,7 @@ export class PsTableComponent implements OnInit, OnChanges, AfterContentInit, On @Input() stateManager: PsTableStateManager = new PsTableUrlStateManager(this.router, this.route); + /** @deprecated use the datasource to detect paginations */ @Output() public page = new EventEmitter(); @ViewChild(PsFlipContainerComponent, { static: true }) public flipContainer: PsFlipContainerComponent | null = null; @@ -159,10 +161,10 @@ export class PsTableComponent implements OnInit, OnChanges, AfterContentInit, On public displayedColumns: string[] = []; - public get sortDirection(): 'asc' | 'desc' { + public get sortDirection(): 'asc' | 'desc' | null { return this.dataSource.sortDirection; } - public set sortDirection(value: 'asc' | 'desc') { + public set sortDirection(value: 'asc' | 'desc' | null) { this.dataSource.sortDirection = value; } @@ -279,21 +281,24 @@ export class PsTableComponent implements OnInit, OnChanges, AfterContentInit, On } public onSearchChanged(value: string) { - this.filterText = value; - this.requestUpdate(); + this.requestUpdate({ + searchText: value, + }); } - public onSortChanged(event: { sortColumn: string; sortDirection: 'asc' | 'desc' }) { - this.sortColumn = event.sortColumn; - this.sortDirection = event.sortDirection; - this.requestUpdate(); + public onSortChanged(event: Sort) { + this.requestUpdate({ + sortColumn: event.active, + sortDirection: event.direction || null, + }); } public onPage(event: PageEvent) { - this.pageIndex = event.pageIndex; - this.pageSize = event.pageSize; this.page.emit(event); - this.requestUpdate(); + this.requestUpdate({ + currentPage: event.pageIndex, + pageSize: event.pageSize, + }); } public onRefreshDataClicked() { @@ -313,8 +318,11 @@ export class PsTableComponent implements OnInit, OnChanges, AfterContentInit, On this.rowDetail.toggle(item, open); } - private requestUpdate() { - const updateInfo = this.dataSource.getUpdateDataInfo(); + private requestUpdate(data: Partial) { + const updateInfo = { + ...this.dataSource.getUpdateDataInfo(), + ...data, + }; this.stateManager.requestUpdate(this.tableId, updateInfo); } @@ -352,7 +360,7 @@ export class PsTableComponent implements OnInit, OnChanges, AfterContentInit, On this.pageIndex = Math.max(urlSettings.currentPage || 0, 0); this.pageSize = Math.max(urlSettings.pageSize || tableSettings.pageSize || 15, 1); this.sortColumn = urlSettings.sortColumn || tableSettings.sortColumn || null; - this.sortDirection = urlSettings.sortDirection || tableSettings.sortDirection || 'asc'; + this.sortDirection = urlSettings.sortDirection || tableSettings.sortDirection || null; this.filterText = urlSettings.searchText || ''; this.displayedColumns = this.columnDefs.map((x) => x.property); diff --git a/projects/components/table/src/table.module.ts b/projects/components/table/src/table.module.ts index d6908773..b5aa6ffb 100644 --- a/projects/components/table/src/table.module.ts +++ b/projects/components/table/src/table.module.ts @@ -74,6 +74,7 @@ import { PsTableComponent } from './table.component'; MatButtonModule, MatMenuModule, MatSelectModule, + MatSortModule, MatInputModule, MatCardModule, MatTooltipModule,