ACS-7461 Remove ADF_DOCUMENT_PARENT_COMPONENT token (#11372)

This commit is contained in:
Denys Vuika
2025-12-15 13:42:21 +00:00
committed by GitHub
parent 8da24884ec
commit 062b8060bc
7 changed files with 98 additions and 94 deletions
@@ -35,7 +35,11 @@
<adf-filter-header <adf-filter-header
[currentFolderId]="currentFolderId" [currentFolderId]="currentFolderId"
[value]="filterValue" [value]="filterValue"
(filterSelection)="onFilterSelectionChange($event)" /> [pagination]="pagination | async"
[sorting]="sortingSubject | async"
(filterSelection)="onFilterSelectionChange($event)"
(searchResultsReady)="onFilterSearchResultsReady($event)"
(filtersCleared)="onFiltersCleared()" />
</div> </div>
<adf-no-content-template> <adf-no-content-template>
@@ -77,7 +77,6 @@ import { PermissionStyleModel } from '../models/permissions-style.model';
import { presetsDefaultModel } from '../models/preset.model'; import { presetsDefaultModel } from '../models/preset.model';
import { DocumentListService } from '../services/document-list.service'; import { DocumentListService } from '../services/document-list.service';
import { LockService } from '../services/lock.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 { FileAutoDownloadComponent } from './file-auto-download/file-auto-download.component';
import { NodeEntityEvent, NodeEntryEvent } from './node.event'; import { NodeEntityEvent, NodeEntryEvent } from './node.event';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
@@ -108,13 +107,7 @@ const BYTES_TO_MB_CONVERSION_VALUE = 1048576;
], ],
templateUrl: './document-list.component.html', templateUrl: './document-list.component.html',
styleUrls: ['./document-list.component.scss'], styleUrls: ['./document-list.component.scss'],
providers: [ providers: [DataTableService],
{
provide: ADF_DOCUMENT_PARENT_COMPONENT,
useExisting: DocumentListComponent
},
DataTableService
],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
host: { class: 'adf-document-list' } host: { class: 'adf-document-list' }
}) })
@@ -1048,6 +1041,16 @@ export class DocumentListComponent extends DataTableSchema implements OnInit, On
this.filterSelection.emit(activeFilters); this.filterSelection.emit(activeFilters);
} }
onFilterSearchResultsReady(nodePaging: NodePaging) {
this.node = nodePaging;
this.reload();
}
onFiltersCleared() {
this.node = null;
this.reload();
}
resetNewFolderPagination() { resetNewFolderPagination() {
this._pagination.skipCount = 0; this._pagination.skipCount = 0;
this._pagination.maxItems = this.maxItems; this._pagination.maxItems = this.maxItems;
@@ -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'
);
@@ -1,9 +1,10 @@
<div *ngIf="isFilterServiceActive"> @if (isFilterServiceActive) {
<adf-header-filter-template> <adf-header-filter-template>
<ng-template let-col> <ng-template let-col>
<adf-search-filter-container [col]="col" <adf-search-filter-container
[value]="value" [col]="col"
(filterChange)="onFilterSelectionChange()" /> [value]="value"
(filterChange)="onFilterSelectionChange()" />
</ng-template> </ng-template>
</adf-header-filter-template> </adf-header-filter-template>
</div> }
@@ -15,17 +15,14 @@
* limitations under the License. * limitations under the License.
*/ */
import { Subject, BehaviorSubject } from 'rxjs'; import { Subject } from 'rxjs';
import { ComponentFixture, TestBed } from '@angular/core/testing'; 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 { SearchService } from '../../../search/services/search.service';
import { ContentTestingModule } from '../../../testing/content.testing.module'; import { ContentTestingModule } from '../../../testing/content.testing.module';
import { SimpleChange } from '@angular/core'; import { SimpleChange } from '@angular/core';
import { SearchHeaderQueryBuilderService } from './../../../search/services/search-header-query-builder.service'; import { SearchHeaderQueryBuilderService } from './../../../search/services/search-header-query-builder.service';
import { DocumentListComponent } from './../document-list.component';
import { FilterHeaderComponent } from './filter-header.component'; import { FilterHeaderComponent } from './filter-header.component';
import { Pagination } from '@alfresco/js-api';
import { ADF_DOCUMENT_PARENT_COMPONENT } from '../document-list.token';
describe('FilterHeaderComponent', () => { describe('FilterHeaderComponent', () => {
let fixture: ComponentFixture<FilterHeaderComponent>; let fixture: ComponentFixture<FilterHeaderComponent>;
@@ -36,28 +33,16 @@ describe('FilterHeaderComponent', () => {
dataLoaded: new Subject() dataLoaded: new Subject()
}; };
const paginationMock = { maxItems: 10, skipCount: 0 }; const paginationMock: PaginationModel = { maxItems: 10, skipCount: 0, totalItems: 0, hasMoreItems: false };
const documentListMock = {
node: 'my-node',
sorting: ['name', 'asc'],
pagination: new BehaviorSubject<Pagination>(paginationMock),
sortingSubject: new BehaviorSubject<DataSorting[]>([]),
reload: () => jasmine.createSpy('reload')
};
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ContentTestingModule, FilterHeaderComponent], imports: [ContentTestingModule, FilterHeaderComponent],
providers: [ providers: [{ provide: SearchService, useValue: searchMock }, DataTableComponent]
{ provide: ADF_DOCUMENT_PARENT_COMPONENT, useExisting: DocumentListComponent },
{ provide: SearchService, useValue: searchMock },
{ provide: DocumentListComponent, useValue: documentListMock },
DataTableComponent
]
}); });
fixture = TestBed.createComponent(FilterHeaderComponent); fixture = TestBed.createComponent(FilterHeaderComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
component.currentFolderId = 'test-folder-id';
queryBuilder = fixture.componentInstance['searchFilterQueryBuilder']; queryBuilder = fixture.componentInstance['searchFilterQueryBuilder'];
}); });
@@ -68,23 +53,26 @@ describe('FilterHeaderComponent', () => {
it('should subscribe to changes in document list pagination', async () => { it('should subscribe to changes in document list pagination', async () => {
const setupCurrentPaginationSpy = spyOn(queryBuilder, 'setupCurrentPagination'); const setupCurrentPaginationSpy = spyOn(queryBuilder, 'setupCurrentPagination');
const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true); component.pagination = paginationMock;
component.ngOnChanges({ currentFolderId: currentFolderNodeIdChange }); const paginationChange = new SimpleChange(undefined, paginationMock, true);
component.ngOnChanges({ pagination: paginationChange });
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
expect(setupCurrentPaginationSpy).toHaveBeenCalled(); expect(setupCurrentPaginationSpy).toHaveBeenCalledWith(paginationMock.maxItems, paginationMock.skipCount);
}); });
it('should subscribe to changes in document list sorting', async () => { it('should subscribe to changes in document list sorting', async () => {
const setSortingSpy = spyOn(queryBuilder, 'setSorting'); const setSortingSpy = spyOn(queryBuilder, 'setSorting');
const sortingMock: DataSorting[] = [new DataSorting('name', 'asc')];
const currentFolderNodeIdChange = new SimpleChange('current-node-id', 'next-node-id', true); component.sorting = sortingMock;
component.ngOnChanges({ currentFolderId: currentFolderNodeIdChange }); const sortingChange = new SimpleChange(undefined, sortingMock, true);
component.ngOnChanges({ sorting: sortingChange });
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
expect(setSortingSpy).toHaveBeenCalled(); expect(setSortingSpy).toHaveBeenCalledWith(sortingMock);
}); });
it('should reset filters after changing the folder node', async () => { it('should reset filters after changing the folder node', async () => {
@@ -143,4 +131,29 @@ describe('FilterHeaderComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable(); 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);
});
}); });
@@ -15,18 +15,17 @@
* limitations under the License. * 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 { DataSorting, HeaderFilterTemplateDirective, PaginationModel } from '@alfresco/adf-core';
import { SearchHeaderQueryBuilderService } from '../../../search/services/search-header-query-builder.service'; import { SearchHeaderQueryBuilderService } from '../../../search/services/search-header-query-builder.service';
import { FilterSearch } from './../../../search/models/filter-search.interface'; 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 { SearchFilterContainerComponent } from '../../../search/components/search-filter-container/search-filter-container.component';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import type { NodePaging } from '@alfresco/js-api';
@Component({ @Component({
selector: 'adf-filter-header', selector: 'adf-filter-header',
imports: [CommonModule, HeaderFilterTemplateDirective, SearchFilterContainerComponent], imports: [HeaderFilterTemplateDirective, SearchFilterContainerComponent],
templateUrl: './filter-header.component.html' templateUrl: './filter-header.component.html'
}) })
export class FilterHeaderComponent implements OnInit, OnChanges { export class FilterHeaderComponent implements OnInit, OnChanges {
@@ -38,26 +37,37 @@ export class FilterHeaderComponent implements OnInit, OnChanges {
@Input({ required: true }) @Input({ required: true })
currentFolderId: string; 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 */ /** Emitted when a filter value is selected */
@Output() @Output()
filterSelection: EventEmitter<FilterSearch[]> = new EventEmitter(); filterSelection: EventEmitter<FilterSearch[]> = new EventEmitter();
/** Emitted when search results are ready */
@Output()
searchResultsReady: EventEmitter<NodePaging> = new EventEmitter();
/** Emitted when filters are cleared and document list should reload */
@Output()
filtersCleared: EventEmitter<void> = new EventEmitter();
isFilterServiceActive: boolean; isFilterServiceActive: boolean;
private readonly searchFilterQueryBuilder = inject(SearchHeaderQueryBuilderService);
private readonly destroyRef = inject(DestroyRef); private readonly destroyRef = inject(DestroyRef);
constructor(@Inject(ADF_DOCUMENT_PARENT_COMPONENT) private documentList: any, private searchFilterQueryBuilder: SearchHeaderQueryBuilderService) {
this.isFilterServiceActive = this.searchFilterQueryBuilder.isFilterServiceActive();
}
ngOnInit() { ngOnInit() {
this.searchFilterQueryBuilder.executed.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((newNodePaging) => { this.searchFilterQueryBuilder.executed.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((resultSetPaging) => {
this.documentList.node = newNodePaging; // ResultSetPaging is structurally compatible with NodePaging for the document list
this.documentList.reload(); // The data adapter can handle both types
this.searchResultsReady.emit(resultSetPaging as unknown as NodePaging);
}); });
this.initDataPagination();
this.initDataSorting();
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
@@ -65,13 +75,22 @@ export class FilterHeaderComponent implements OnInit, OnChanges {
this.resetFilterHeader(); this.resetFilterHeader();
this.configureSearchParent(changes['currentFolderId'].currentValue); 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() { onFilterSelectionChange() {
this.filterSelection.emit(this.searchFilterQueryBuilder.getActiveFilters()); this.filterSelection.emit(this.searchFilterQueryBuilder.getActiveFilters());
if (this.searchFilterQueryBuilder.isNoFilterActive()) { if (this.searchFilterQueryBuilder.isNoFilterActive()) {
this.documentList.node = null; this.filtersCleared.emit();
this.documentList.reload();
} }
} }
@@ -79,18 +98,6 @@ export class FilterHeaderComponent implements OnInit, OnChanges {
this.searchFilterQueryBuilder.resetActiveFilters(); 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) { private configureSearchParent(currentFolderId: string) {
if (this.searchFilterQueryBuilder.isCustomSourceNode(currentFolderId)) { if (this.searchFilterQueryBuilder.isCustomSourceNode(currentFolderId)) {
this.searchFilterQueryBuilder.getNodeIdForCustomSource(currentFolderId).subscribe((node) => { this.searchFilterQueryBuilder.getNodeIdForCustomSource(currentFolderId).subscribe((node) => {
@@ -93,7 +93,8 @@ export class DataTableCellComponent implements OnInit {
constructor() { constructor() {
// Listen to locale changes and re-compute the title with the latest value // Listen to locale changes and re-compute the title with the latest value
effect(() => { 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 // When locale changes, re-compute title using the stored latest value
this.recomputeTitle(); this.recomputeTitle();
}); });