diff --git a/lib/content-services/src/lib/document-list/components/document-list.component.html b/lib/content-services/src/lib/document-list/components/document-list.component.html index 8a62465786..6bfa1572b4 100644 --- a/lib/content-services/src/lib/document-list/components/document-list.component.html +++ b/lib/content-services/src/lib/document-list/components/document-list.component.html @@ -35,7 +35,11 @@ + [pagination]="pagination | async" + [sorting]="sortingSubject | async" + (filterSelection)="onFilterSelectionChange($event)" + (searchResultsReady)="onFilterSearchResultsReady($event)" + (filtersCleared)="onFiltersCleared()" /> diff --git a/lib/content-services/src/lib/document-list/components/document-list.component.ts b/lib/content-services/src/lib/document-list/components/document-list.component.ts index f2d867d4e4..3e7641fbce 100644 --- a/lib/content-services/src/lib/document-list/components/document-list.component.ts +++ b/lib/content-services/src/lib/document-list/components/document-list.component.ts @@ -77,7 +77,6 @@ import { PermissionStyleModel } from '../models/permissions-style.model'; import { presetsDefaultModel } from '../models/preset.model'; import { DocumentListService } from '../services/document-list.service'; import { LockService } from '../services/lock.service'; -import { ADF_DOCUMENT_PARENT_COMPONENT } from './document-list.token'; import { FileAutoDownloadComponent } from './file-auto-download/file-auto-download.component'; import { NodeEntityEvent, NodeEntryEvent } from './node.event'; import { CommonModule } from '@angular/common'; @@ -108,13 +107,7 @@ const BYTES_TO_MB_CONVERSION_VALUE = 1048576; ], templateUrl: './document-list.component.html', styleUrls: ['./document-list.component.scss'], - providers: [ - { - provide: ADF_DOCUMENT_PARENT_COMPONENT, - useExisting: DocumentListComponent - }, - DataTableService - ], + providers: [DataTableService], encapsulation: ViewEncapsulation.None, host: { class: 'adf-document-list' } }) @@ -1048,6 +1041,16 @@ export class DocumentListComponent extends DataTableSchema implements OnInit, On this.filterSelection.emit(activeFilters); } + onFilterSearchResultsReady(nodePaging: NodePaging) { + this.node = nodePaging; + this.reload(); + } + + onFiltersCleared() { + this.node = null; + this.reload(); + } + resetNewFolderPagination() { this._pagination.skipCount = 0; this._pagination.maxItems = this.maxItems; diff --git a/lib/content-services/src/lib/document-list/components/document-list.token.ts b/lib/content-services/src/lib/document-list/components/document-list.token.ts deleted file mode 100644 index 8981c29829..0000000000 --- a/lib/content-services/src/lib/document-list/components/document-list.token.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*! - * @license - * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable rxjs/no-subject-value */ -/* eslint-disable @typescript-eslint/naming-convention */ - -import { InjectionToken } from '@angular/core'; - -export const ADF_DOCUMENT_PARENT_COMPONENT = new InjectionToken( - 'ADF_DOCUMENT_PARENT_COMPONENT' -); diff --git a/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.html b/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.html index e4e4229d00..648caef530 100644 --- a/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.html +++ b/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.html @@ -1,9 +1,10 @@ -
+@if (isFilterServiceActive) { - + -
+} diff --git a/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.spec.ts b/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.spec.ts index 49be25fdc9..e9791f31dd 100644 --- a/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.spec.ts +++ b/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.spec.ts @@ -15,17 +15,14 @@ * limitations under the License. */ -import { Subject, BehaviorSubject } from 'rxjs'; +import { Subject } from 'rxjs'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { DataTableComponent, DataSorting } from '@alfresco/adf-core'; +import { DataTableComponent, DataSorting, PaginationModel } from '@alfresco/adf-core'; import { SearchService } from '../../../search/services/search.service'; import { ContentTestingModule } from '../../../testing/content.testing.module'; import { SimpleChange } from '@angular/core'; import { SearchHeaderQueryBuilderService } from './../../../search/services/search-header-query-builder.service'; -import { DocumentListComponent } from './../document-list.component'; import { FilterHeaderComponent } from './filter-header.component'; -import { Pagination } from '@alfresco/js-api'; -import { ADF_DOCUMENT_PARENT_COMPONENT } from '../document-list.token'; describe('FilterHeaderComponent', () => { let fixture: ComponentFixture; @@ -36,28 +33,16 @@ describe('FilterHeaderComponent', () => { dataLoaded: new Subject() }; - const paginationMock = { maxItems: 10, skipCount: 0 }; - - const documentListMock = { - node: 'my-node', - sorting: ['name', 'asc'], - pagination: new BehaviorSubject(paginationMock), - sortingSubject: new BehaviorSubject([]), - reload: () => jasmine.createSpy('reload') - }; + const paginationMock: PaginationModel = { maxItems: 10, skipCount: 0, totalItems: 0, hasMoreItems: false }; beforeEach(() => { TestBed.configureTestingModule({ imports: [ContentTestingModule, FilterHeaderComponent], - providers: [ - { provide: ADF_DOCUMENT_PARENT_COMPONENT, useExisting: DocumentListComponent }, - { provide: SearchService, useValue: searchMock }, - { provide: DocumentListComponent, useValue: documentListMock }, - DataTableComponent - ] + providers: [{ provide: SearchService, useValue: searchMock }, DataTableComponent] }); fixture = TestBed.createComponent(FilterHeaderComponent); component = fixture.componentInstance; + component.currentFolderId = 'test-folder-id'; queryBuilder = fixture.componentInstance['searchFilterQueryBuilder']; }); @@ -68,23 +53,26 @@ describe('FilterHeaderComponent', () => { it('should subscribe to changes in document list pagination', async () => { const setupCurrentPaginationSpy = spyOn(queryBuilder, 'setupCurrentPagination'); - const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true); - component.ngOnChanges({ currentFolderId: currentFolderNodeIdChange }); + component.pagination = paginationMock; + const paginationChange = new SimpleChange(undefined, paginationMock, true); + component.ngOnChanges({ pagination: paginationChange }); fixture.detectChanges(); await fixture.whenStable(); - expect(setupCurrentPaginationSpy).toHaveBeenCalled(); + expect(setupCurrentPaginationSpy).toHaveBeenCalledWith(paginationMock.maxItems, paginationMock.skipCount); }); it('should subscribe to changes in document list sorting', async () => { const setSortingSpy = spyOn(queryBuilder, 'setSorting'); + const sortingMock: DataSorting[] = [new DataSorting('name', 'asc')]; - const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true); - component.ngOnChanges({ currentFolderId: currentFolderNodeIdChange }); + component.sorting = sortingMock; + const sortingChange = new SimpleChange(undefined, sortingMock, true); + component.ngOnChanges({ sorting: sortingChange }); fixture.detectChanges(); await fixture.whenStable(); - expect(setSortingSpy).toHaveBeenCalled(); + expect(setSortingSpy).toHaveBeenCalledWith(sortingMock); }); it('should reset filters after changing the folder node', async () => { @@ -143,4 +131,29 @@ describe('FilterHeaderComponent', () => { fixture.detectChanges(); fixture.whenStable(); }); + + it('should emit filtersCleared when no filters are active', (done) => { + spyOn(queryBuilder, 'getActiveFilters').and.returnValue([]); + spyOn(queryBuilder, 'isNoFilterActive').and.returnValue(true); + + component.filtersCleared.subscribe(() => { + done(); + }); + + component.onFilterSelectionChange(); + fixture.detectChanges(); + }); + + it('should emit searchResultsReady when search query builder executes', (done) => { + fixture.detectChanges(); // Initialize component (triggers ngOnInit) + + const mockNodePaging: any = { list: { entries: [] } }; + + component.searchResultsReady.subscribe((nodePaging) => { + expect(nodePaging).toBe(mockNodePaging); + done(); + }); + + queryBuilder.executed.next(mockNodePaging); + }); }); diff --git a/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.ts b/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.ts index c168652882..6ac7172e8e 100644 --- a/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.ts +++ b/lib/content-services/src/lib/document-list/components/filter-header/filter-header.component.ts @@ -15,18 +15,17 @@ * limitations under the License. */ -import { Component, DestroyRef, EventEmitter, Inject, inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, DestroyRef, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { DataSorting, HeaderFilterTemplateDirective, PaginationModel } from '@alfresco/adf-core'; import { SearchHeaderQueryBuilderService } from '../../../search/services/search-header-query-builder.service'; import { FilterSearch } from './../../../search/models/filter-search.interface'; -import { ADF_DOCUMENT_PARENT_COMPONENT } from '../document-list.token'; -import { CommonModule } from '@angular/common'; import { SearchFilterContainerComponent } from '../../../search/components/search-filter-container/search-filter-container.component'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import type { NodePaging } from '@alfresco/js-api'; @Component({ selector: 'adf-filter-header', - imports: [CommonModule, HeaderFilterTemplateDirective, SearchFilterContainerComponent], + imports: [HeaderFilterTemplateDirective, SearchFilterContainerComponent], templateUrl: './filter-header.component.html' }) export class FilterHeaderComponent implements OnInit, OnChanges { @@ -38,26 +37,37 @@ export class FilterHeaderComponent implements OnInit, OnChanges { @Input({ required: true }) currentFolderId: string; + /** Pagination model from the document list */ + @Input() + pagination: PaginationModel; + + /** Sorting configuration from the document list */ + @Input() + sorting: DataSorting[]; + /** Emitted when a filter value is selected */ @Output() filterSelection: EventEmitter = new EventEmitter(); + /** Emitted when search results are ready */ + @Output() + searchResultsReady: EventEmitter = new EventEmitter(); + + /** Emitted when filters are cleared and document list should reload */ + @Output() + filtersCleared: EventEmitter = new EventEmitter(); + isFilterServiceActive: boolean; + private readonly searchFilterQueryBuilder = inject(SearchHeaderQueryBuilderService); private readonly destroyRef = inject(DestroyRef); - constructor(@Inject(ADF_DOCUMENT_PARENT_COMPONENT) private documentList: any, private searchFilterQueryBuilder: SearchHeaderQueryBuilderService) { - this.isFilterServiceActive = this.searchFilterQueryBuilder.isFilterServiceActive(); - } - ngOnInit() { - this.searchFilterQueryBuilder.executed.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((newNodePaging) => { - this.documentList.node = newNodePaging; - this.documentList.reload(); + this.searchFilterQueryBuilder.executed.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((resultSetPaging) => { + // ResultSetPaging is structurally compatible with NodePaging for the document list + // The data adapter can handle both types + this.searchResultsReady.emit(resultSetPaging as unknown as NodePaging); }); - - this.initDataPagination(); - this.initDataSorting(); } ngOnChanges(changes: SimpleChanges) { @@ -65,13 +75,22 @@ export class FilterHeaderComponent implements OnInit, OnChanges { this.resetFilterHeader(); this.configureSearchParent(changes['currentFolderId'].currentValue); } + + if (changes['pagination']?.currentValue) { + const pagination = changes['pagination'].currentValue as PaginationModel; + this.searchFilterQueryBuilder.setupCurrentPagination(pagination.maxItems, pagination.skipCount); + } + + if (changes['sorting']?.currentValue) { + const sorting = changes['sorting'].currentValue as DataSorting[]; + this.searchFilterQueryBuilder.setSorting(sorting); + } } onFilterSelectionChange() { this.filterSelection.emit(this.searchFilterQueryBuilder.getActiveFilters()); if (this.searchFilterQueryBuilder.isNoFilterActive()) { - this.documentList.node = null; - this.documentList.reload(); + this.filtersCleared.emit(); } } @@ -79,18 +98,6 @@ export class FilterHeaderComponent implements OnInit, OnChanges { this.searchFilterQueryBuilder.resetActiveFilters(); } - initDataPagination() { - this.documentList.pagination.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((newPagination: PaginationModel) => { - this.searchFilterQueryBuilder.setupCurrentPagination(newPagination.maxItems, newPagination.skipCount); - }); - } - - initDataSorting() { - this.documentList.sortingSubject.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((sorting: DataSorting[]) => { - this.searchFilterQueryBuilder.setSorting(sorting); - }); - } - private configureSearchParent(currentFolderId: string) { if (this.searchFilterQueryBuilder.isCustomSourceNode(currentFolderId)) { this.searchFilterQueryBuilder.getNodeIdForCustomSource(currentFolderId).subscribe((node) => { diff --git a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts index 649627eaa7..4c23659ecf 100644 --- a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts +++ b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts @@ -93,7 +93,8 @@ export class DataTableCellComponent implements OnInit { constructor() { // Listen to locale changes and re-compute the title with the latest value effect(() => { - this.userPreferencesService.localeSignal(); + // Read the signal value to track changes + this.userPreferencesService.localeSignal?.(); // When locale changes, re-compute title using the stored latest value this.recomputeTitle(); });