mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-06-30 18:15:11 +00:00
[ACS-5566] - Add configurable columns to document list (#8968)
* [ACS-5566] - add configurable columns to document list * [ACS-5566] - add max visible columns * [ACS-5566] - add unit tests * [ACS-5566] - add docs * [ACS-5566] - style changes after CR * [ACS-5566] - changes for pipeline * [ACS-5566] - changes for pipeline * [ACS-5566] - changes for pipeline
This commit is contained in:
parent
3912a652d5
commit
36c6e6d8ea
@ -60,6 +60,7 @@ Displays the documents from a repository.
|
||||
| ---- | ---- | ------------- | ----------- |
|
||||
| additionalSorting | [`DataSorting`](../../../lib/core/src/lib/datatable/data/data-sorting.model.ts) | | Defines default sorting. The format is an array of strings `[key direction, otherKey otherDirection]` i.e. `['name desc', 'nodeType asc']` or `['name asc']`. Set this value if you want a base rule to be added to the sorting apart from the one driven by the header. |
|
||||
| allowDropFiles | `boolean` | false | When true, this enables you to drop files directly into subfolders shown as items in the list or into another file to trigger updating it's version. When false, the dropped file will be added to the current folder (ie, the one containing all the items shown in the list). See the [Upload directive](../../core/directives/upload.directive.md) for further details about how the file drop is handled. |
|
||||
| columnsPresetKey | `string` | | Key of columns preset set in extension.json|
|
||||
| contentActions | `boolean` | false | Toggles content actions for each row |
|
||||
| contentActionsPosition | `string` | "right" | Position of the content actions dropdown menu. Can be set to "left" or "right". |
|
||||
| contextMenuActions | `boolean` | false | Toggles context menus for each row |
|
||||
@ -72,6 +73,7 @@ Displays the documents from a repository.
|
||||
| includeFields | `string[]` | | Include additional information about the node in the server request. For example: association, isLink, isLocked and others. |
|
||||
| loading | `boolean` | false | Toggles the loading state and animated spinners for the component. Used in combination with `navigate=false` to perform custom navigation and loading state indication. |
|
||||
| locationFormat | `string` | "/" | The default route for all the location-based columns (if declared). |
|
||||
| maxColumnsVisible | `number` | | Limit of possible visible columns, including "$thumbnail" column if provided |
|
||||
| maxItems | `number` | | Default value is stored in the user preference settings. Use this only if you are not using pagination. |
|
||||
| multiselect | `boolean` | false | Toggles multiselect mode |
|
||||
| navigate | `boolean` | true | Toggles navigation to folder content or file preview |
|
||||
|
@ -8,6 +8,7 @@
|
||||
[contextMenu]="contextMenuActions"
|
||||
[rowStyle]="rowStyle"
|
||||
[rowStyleClass]="rowStyleClass"
|
||||
[showMainDatatableActions]="true"
|
||||
[loading]="loading"
|
||||
[display]="display"
|
||||
[noPermission]="noPermission"
|
||||
@ -72,4 +73,16 @@
|
||||
</ng-template>
|
||||
</adf-loading-content-template>
|
||||
|
||||
<adf-main-menu-datatable-template>
|
||||
<ng-template let-mainMenuTrigger>
|
||||
<adf-datatable-column-selector
|
||||
[columns]="data.getColumns()"
|
||||
[mainMenuTrigger]="mainMenuTrigger"
|
||||
[columnsSorting]="false"
|
||||
[maxColumnsVisible]="maxColumnsVisible"
|
||||
(submitColumnsVisibility)="onColumnsVisibilityChange($event)">
|
||||
</adf-datatable-column-selector>
|
||||
</ng-template>
|
||||
</adf-main-menu-datatable-template>
|
||||
|
||||
</adf-datatable>
|
||||
|
@ -132,57 +132,39 @@ describe('DocumentList', () => {
|
||||
});
|
||||
};
|
||||
|
||||
it('should load -trashcan- preset', async () => {
|
||||
documentList.currentFolderId = '-trashcan-';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
it('should load -trashcan- preset', () => {
|
||||
documentList.presetColumn = '-trashcan-';
|
||||
documentList.ngAfterContentInit();
|
||||
validatePreset(['$thumbnail', 'name', 'path', 'content.sizeInBytes', 'archivedAt', 'archivedByUser.displayName']);
|
||||
});
|
||||
|
||||
it('should load -sites- preset', async () => {
|
||||
documentList.currentFolderId = '-sites-';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
documentList.presetColumn = '-sites-';
|
||||
documentList.ngAfterContentInit();
|
||||
validatePreset(['$thumbnail', 'title', 'visibility']);
|
||||
});
|
||||
|
||||
it('shuld load -mysites- preset', async () => {
|
||||
documentList.currentFolderId = '-mysites-';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
documentList.presetColumn = '-mysites-';
|
||||
documentList.ngAfterContentInit();
|
||||
validatePreset(['$thumbnail', 'title', 'visibility']);
|
||||
});
|
||||
|
||||
it('should load -favorites- preset', async () => {
|
||||
documentList.currentFolderId = '-favorites-';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
documentList.presetColumn = '-favorites-';
|
||||
documentList.ngAfterContentInit();
|
||||
validatePreset(['$thumbnail', 'name', 'path', 'content.sizeInBytes', 'modifiedAt', 'modifiedByUser.displayName']);
|
||||
});
|
||||
|
||||
it('should load -recent- preset', async () => {
|
||||
documentList.currentFolderId = '-recent-';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
documentList.presetColumn = '-recent-';
|
||||
documentList.ngAfterContentInit();
|
||||
validatePreset(['$thumbnail', 'name', 'path', 'content.sizeInBytes', 'modifiedAt']);
|
||||
});
|
||||
|
||||
it('should load -sharedlinks- preset', async () => {
|
||||
documentList.currentFolderId = '-sharedlinks-';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
documentList.presetColumn = '-sharedlinks-';
|
||||
documentList.ngAfterContentInit();
|
||||
validatePreset([
|
||||
'$thumbnail',
|
||||
'name',
|
||||
@ -195,11 +177,8 @@ describe('DocumentList', () => {
|
||||
});
|
||||
|
||||
it('should load default preset', async () => {
|
||||
documentList.currentFolderId = 'f5dacdb9-6d07-4fe9-9f2a-dedc21bae603';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
documentList.presetColumn = 'f5dacdb9-6d07-4fe9-9f2a-dedc21bae603';
|
||||
documentList.ngAfterContentInit();
|
||||
validatePreset(['$thumbnail', 'name', 'content.sizeInBytes', 'modifiedAt', 'modifiedByUser.displayName']);
|
||||
});
|
||||
});
|
||||
|
@ -38,13 +38,11 @@ import { ContentService } from '../../common/services/content.service';
|
||||
|
||||
import {
|
||||
DataCellEvent,
|
||||
DataColumn,
|
||||
DataRowActionEvent,
|
||||
DataSorting,
|
||||
DataTableComponent,
|
||||
DisplayMode,
|
||||
ShowHeaderMode,
|
||||
ObjectDataColumn,
|
||||
PaginatedComponent,
|
||||
AppConfigService,
|
||||
DataColumnListComponent,
|
||||
@ -58,7 +56,9 @@ import {
|
||||
AlfrescoApiService,
|
||||
UserPreferenceValues,
|
||||
DataRow,
|
||||
DataTableService
|
||||
DataTableService,
|
||||
DataTableSchema,
|
||||
DataColumn
|
||||
} from '@alfresco/adf-core';
|
||||
import { NodesApiService } from '../../common/services/nodes-api.service';
|
||||
|
||||
@ -97,7 +97,7 @@ const BYTES_TO_MB_CONVERSION_VALUE = 1048576;
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-document-list' }
|
||||
})
|
||||
export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, AfterContentInit, PaginatedComponent, NavigableComponentInterface {
|
||||
export class DocumentListComponent extends DataTableSchema implements OnInit, OnChanges, OnDestroy, AfterContentInit, PaginatedComponent, NavigableComponentInterface {
|
||||
static SINGLE_CLICK_NAVIGATION: string = 'click';
|
||||
static DOUBLE_CLICK_NAVIGATION: string = 'dblclick';
|
||||
|
||||
@ -315,6 +315,14 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
@Input()
|
||||
maxItems: number = this.DEFAULT_PAGINATION.maxItems;
|
||||
|
||||
/** Key of columns preset set in extension.json */
|
||||
@Input()
|
||||
columnsPresetKey?: string;
|
||||
|
||||
/** Limit of possible visible columns, including "$thumbnail" column if provided */
|
||||
@Input()
|
||||
maxColumnsVisible?: number;
|
||||
|
||||
/** Emitted when the user clicks a list node */
|
||||
@Output()
|
||||
nodeClick = new EventEmitter<NodeEntityEvent>();
|
||||
@ -371,7 +379,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
pagination: BehaviorSubject<PaginationModel> = new BehaviorSubject<PaginationModel>(this.DEFAULT_PAGINATION);
|
||||
sortingSubject: BehaviorSubject<DataSorting[]> = new BehaviorSubject<DataSorting[]>(this.DEFAULT_SORTING);
|
||||
|
||||
private layoutPresets = {};
|
||||
private rowMenuCache: { [key: string]: ContentActionModel[] } = {};
|
||||
private loadingTimeout: any;
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
@ -395,6 +402,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
private lockService: LockService,
|
||||
private dialog: MatDialog
|
||||
) {
|
||||
super(appConfig, 'default', presetsDefaultModel);
|
||||
this.nodeService.nodeUpdated.pipe(takeUntil(this.onDestroy$)).subscribe((node) => {
|
||||
this.dataTableService.rowUpdate.next({ id: node.id, obj: { entry: node } });
|
||||
});
|
||||
@ -421,10 +429,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
return null;
|
||||
}
|
||||
|
||||
private get hasCustomLayout(): boolean {
|
||||
return this.columnList?.columns?.length > 0;
|
||||
}
|
||||
|
||||
private getDefaultSorting(): DataSorting {
|
||||
let defaultSorting: DataSorting;
|
||||
if (Array.isArray(this.sorting)) {
|
||||
@ -437,9 +441,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
return defaultSorting;
|
||||
}
|
||||
|
||||
private getLayoutPreset(name: string = 'default'): DataColumn[] {
|
||||
return (this.layoutPresets[name] || this.layoutPresets['default']).map((col) => new ObjectDataColumn(col));
|
||||
}
|
||||
|
||||
isMobile(): boolean {
|
||||
return !!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
@ -477,34 +478,21 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
if (this.filterValue && Object.keys(this.filterValue).length > 0) {
|
||||
this.showHeader = ShowHeaderMode.Always;
|
||||
}
|
||||
if (this.columnsPresetKey) {
|
||||
this.setPresetKey(this.columnsPresetKey);
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
if (this.columnList) {
|
||||
this.columnList.columns.changes.pipe(takeUntil(this.onDestroy$)).subscribe(() => this.setTableSchema());
|
||||
this.columnList.columns.changes.pipe(takeUntil(this.onDestroy$)).subscribe(() => {
|
||||
this.createColumns();
|
||||
this.data.setColumns(this.columns);
|
||||
});
|
||||
}
|
||||
this.setTableSchema();
|
||||
this.createDatatableSchema();
|
||||
this.data.setColumns(this.columns);
|
||||
}
|
||||
|
||||
private setTableSchema() {
|
||||
let schema: DataColumn[] = [];
|
||||
|
||||
if (this.hasCustomLayout) {
|
||||
schema = this.columnList.columns.map((c) => c as DataColumn);
|
||||
}
|
||||
|
||||
if (!this.data) {
|
||||
this.data = new ShareDataTableAdapter(this.thumbnailService, this.contentService, schema, this.getDefaultSorting(), this.sortingMode);
|
||||
} else if (schema && schema.length > 0) {
|
||||
this.data.setColumns(schema);
|
||||
}
|
||||
|
||||
const columns = this.data.getColumns();
|
||||
if (!columns || columns.length === 0) {
|
||||
this.setupDefaultColumns(this.currentFolderId);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (!changes['preselectNodes']) {
|
||||
this.resetSelection();
|
||||
@ -718,10 +706,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
this.setLoadingState(true);
|
||||
}
|
||||
|
||||
if (!this.hasCustomLayout) {
|
||||
this.setupDefaultColumns(this.currentFolderId);
|
||||
}
|
||||
|
||||
if (this.documentListService.isCustomSourceService(this.currentFolderId)) {
|
||||
this.updateCustomSourceData(this.currentFolderId);
|
||||
}
|
||||
@ -772,17 +756,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
return [`${this.additionalSorting.key} ${this.additionalSorting.direction}`, `${currentKey} ${currentDirection}`];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a set of predefined columns.
|
||||
* @param preset preset to use
|
||||
*/
|
||||
private setupDefaultColumns(preset: string = 'default'): void {
|
||||
if (this.data) {
|
||||
const columns = this.getLayoutPreset(preset);
|
||||
this.data.setColumns(columns);
|
||||
}
|
||||
}
|
||||
|
||||
onPreviewFile(node: NodeEntry) {
|
||||
if (node) {
|
||||
const sizeInMB = node.entry?.content?.sizeInBytes / BYTES_TO_MB_CONVERSION_VALUE;
|
||||
@ -797,7 +770,9 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onColumnsVisibilityChange(updatedColumns: Array<DataColumn>): void {
|
||||
this.data.setColumns(updatedColumns);
|
||||
}
|
||||
onNodeClick(nodeEntry: NodeEntry) {
|
||||
const domEvent = new CustomEvent('node-click', {
|
||||
detail: {
|
||||
@ -932,10 +907,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
||||
return canNavigateFolder;
|
||||
}
|
||||
|
||||
private loadLayoutPresets(): void {
|
||||
const externalSettings = this.appConfig.get('document-list.presets', null);
|
||||
this.layoutPresets = externalSettings ? Object.assign({}, presetsDefaultModel, externalSettings) : presetsDefaultModel;
|
||||
}
|
||||
|
||||
private onDataReady(nodePaging: NodePaging) {
|
||||
this.ready.emit(nodePaging);
|
||||
|
@ -43,6 +43,7 @@
|
||||
class="adf-columns-selector-column-checkbox"
|
||||
[attr.data-automation-id]="'adf-columns-selector-column-checkbox-' + column.title"
|
||||
[checked]="!column.isHidden"
|
||||
[disabled]="isCheckboxDisabled(column)"
|
||||
(change)="changeColumnVisibility(column)">
|
||||
<div class="adf-columns-selector-list-content">{{translatedTitle}}</div>
|
||||
</mat-checkbox>
|
||||
|
@ -156,19 +156,35 @@ describe('ColumnsSelectorComponent', () => {
|
||||
expect(toggledColumnItem.isHidden).toBe(true);
|
||||
});
|
||||
|
||||
it('should set proper default state for checkboxes', async () => {
|
||||
menuOpenedTrigger.next();
|
||||
fixture.detectChanges();
|
||||
|
||||
const checkboxes = await loader.getAllHarnesses(MatCheckboxHarness);
|
||||
describe('checkboxes', () => {
|
||||
it('should have set proper default state', async () => {
|
||||
menuOpenedTrigger.next();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(await checkboxes[0].isChecked()).toBe(true);
|
||||
expect(await checkboxes[1].isChecked()).toBe(true);
|
||||
expect(await checkboxes[2].isChecked()).toBe(true);
|
||||
expect(await checkboxes[3].isChecked()).toBe(false);
|
||||
const checkboxes = await loader.getAllHarnesses(MatCheckboxHarness);
|
||||
|
||||
expect(await checkboxes[0].isChecked()).toBe(true);
|
||||
expect(await checkboxes[1].isChecked()).toBe(true);
|
||||
expect(await checkboxes[2].isChecked()).toBe(true);
|
||||
expect(await checkboxes[3].isChecked()).toBe(false);
|
||||
});
|
||||
|
||||
it('should be disabled when visible columns limit is reached', async () => {
|
||||
component.maxColumnsVisible = 4;
|
||||
menuOpenedTrigger.next();
|
||||
fixture.detectChanges();
|
||||
|
||||
const checkboxes = await loader.getAllHarnesses(MatCheckboxHarness);
|
||||
|
||||
expect(await checkboxes[0].isDisabled()).toBe(false);
|
||||
expect(await checkboxes[1].isDisabled()).toBe(false);
|
||||
expect(await checkboxes[2].isDisabled()).toBe(false);
|
||||
expect(await checkboxes[3].isDisabled()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show hidden columns at the end of the list', async () => {
|
||||
describe('sorting', () => {
|
||||
const hiddenDataColumn: DataColumn = {
|
||||
id: 'hiddenDataColumn',
|
||||
title: 'hiddenDataColumn',
|
||||
@ -183,14 +199,27 @@ describe('ColumnsSelectorComponent', () => {
|
||||
key: 'shownDataColumn',
|
||||
type: 'text'
|
||||
};
|
||||
it('should show hidden columns at the end of the list by default', async () => {
|
||||
component.columns = [hiddenDataColumn, shownDataColumn];
|
||||
menuOpenedTrigger.next();
|
||||
fixture.detectChanges();
|
||||
|
||||
component.columns = [hiddenDataColumn, shownDataColumn];
|
||||
menuOpenedTrigger.next();
|
||||
fixture.detectChanges();
|
||||
const checkboxes = await loader.getAllHarnesses(MatCheckboxHarness);
|
||||
|
||||
const checkboxes = await loader.getAllHarnesses(MatCheckboxHarness);
|
||||
expect(await checkboxes[0].getLabelText()).toBe(shownDataColumn.title);
|
||||
expect(await checkboxes[1].getLabelText()).toBe(hiddenDataColumn.title);
|
||||
});
|
||||
|
||||
expect(await checkboxes[0].getLabelText()).toBe(shownDataColumn.title);
|
||||
expect(await checkboxes[1].getLabelText()).toBe(hiddenDataColumn.title);
|
||||
it('should NOT show hidden columns at the end of the list if sorting is disabled', async () => {
|
||||
component.columns = [hiddenDataColumn, shownDataColumn];
|
||||
component.columnsSorting = false;
|
||||
menuOpenedTrigger.next();
|
||||
fixture.detectChanges();
|
||||
|
||||
const checkboxes = await loader.getAllHarnesses(MatCheckboxHarness);
|
||||
|
||||
expect(await checkboxes[0].getLabelText()).toBe(hiddenDataColumn.title);
|
||||
expect(await checkboxes[1].getLabelText()).toBe(shownDataColumn.title);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -34,6 +34,12 @@ export class ColumnsSelectorComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
mainMenuTrigger: MatMenuTrigger;
|
||||
|
||||
@Input()
|
||||
columnsSorting = true;
|
||||
|
||||
@Input()
|
||||
maxColumnsVisible?: number;
|
||||
|
||||
@Output()
|
||||
submitColumnsVisibility = new EventEmitter<DataColumn[]>();
|
||||
|
||||
@ -47,7 +53,7 @@ export class ColumnsSelectorComponent implements OnInit, OnDestroy {
|
||||
takeUntil(this.onDestroy$)
|
||||
).subscribe(() => {
|
||||
const columns = this.columns.map(column => ({...column}));
|
||||
this.columnItems = this.sortColumns(columns);
|
||||
this.columnItems = this.columnsSorting ? this.sortColumns(columns) : columns;
|
||||
});
|
||||
|
||||
this.mainMenuTrigger.menuClosed.pipe(
|
||||
@ -82,6 +88,10 @@ export class ColumnsSelectorComponent implements OnInit, OnDestroy {
|
||||
this.closeMenu();
|
||||
}
|
||||
|
||||
isCheckboxDisabled(column: DataColumn): boolean {
|
||||
return this.maxColumnsVisible && column.isHidden && this.maxColumnsVisible === this.columnItems.filter(dataColumn => !dataColumn.isHidden).length;
|
||||
}
|
||||
|
||||
private sortColumns(columns: DataColumn[]): DataColumn[] {
|
||||
const shownColumns = columns.filter(column => !column.isHidden);
|
||||
const hiddenColumns = columns.filter(column => column.isHidden);
|
||||
|
@ -49,7 +49,6 @@ export abstract class DataTableSchema<T = unknown> {
|
||||
|
||||
public createDatatableSchema(): void {
|
||||
this.loadLayoutPresets();
|
||||
|
||||
if (!this.columns || this.columns.length === 0) {
|
||||
this.createColumns();
|
||||
this.columnsSchemaSubject$.next(true);
|
||||
@ -98,7 +97,7 @@ export abstract class DataTableSchema<T = unknown> {
|
||||
}
|
||||
|
||||
public getSchemaFromConfig(presetColumn: string): DataColumn[] {
|
||||
return presetColumn ? this.layoutPresets[presetColumn].map((col) => new ObjectDataColumn(col)) : [];
|
||||
return presetColumn && this.layoutPresets[presetColumn] ? this.layoutPresets[presetColumn].map((col) => new ObjectDataColumn(col)) : [];
|
||||
}
|
||||
|
||||
private getDefaultLayoutPreset(): DataColumn[] {
|
||||
|
@ -44,6 +44,7 @@ export interface DocumentListPresetRef extends ExtensionElement {
|
||||
template: string;
|
||||
desktopOnly: boolean;
|
||||
sortingKey: string;
|
||||
isHidden?: boolean;
|
||||
rules?: {
|
||||
[key: string]: string;
|
||||
visible?: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user