mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[AAE-4841] - Fix content node selector current selection is lost after uploading files (#6862)
* [AAE-4841] - Preserve current selection when preselecting the newly uploaded nodes * add a method to handle unselection of a preselected row in document list and emit the change * Fix-refactor unselection * Fix document list reload * Partial revert share datatable adapter * try with overwriting datatable selection * Sync datatable selection after every page load * refactor selection/unselection in datatable by using a row id * Fix/Add some unit tests * Move preselection from adapter to doc list, fix single selection mode * Add some unit tests * Add document list unit tests
This commit is contained in:
@@ -19,7 +19,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|||||||
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { Node, NodeEntry, NodePaging, RequestScope, ResultSetPaging, SiteEntry, SitePaging } from '@alfresco/js-api';
|
import { Node, NodeEntry, NodePaging, RequestScope, ResultSetPaging, SiteEntry, SitePaging } from '@alfresco/js-api';
|
||||||
import { AppConfigService, FileModel, FileUploadStatus, NodesApiService, setupTestBed, SitesService, UploadService } from '@alfresco/adf-core';
|
import { AppConfigService, FileModel, FileUploadStatus, NodesApiService, setupTestBed, SitesService, UploadService, FileUploadCompleteEvent } from '@alfresco/adf-core';
|
||||||
import { of, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
import { DropdownBreadcrumbComponent } from '../breadcrumb';
|
import { DropdownBreadcrumbComponent } from '../breadcrumb';
|
||||||
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
|
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
|
||||||
@@ -1214,18 +1214,74 @@ describe('ContentNodeSelectorPanelComponent', () => {
|
|||||||
spyOn(documentListService, 'getFolder');
|
spyOn(documentListService, 'getFolder');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove the node from the chosenNodes when an upload gets deleted', () => {
|
it('should trigger preselection only after the upload batch contains all the uploads of the queue and no other uploads are in progress', fakeAsync(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const selectSpy = spyOn(component.select, 'next');
|
component.selectionMode = 'multiple';
|
||||||
|
|
||||||
|
const isUploadingSpy = spyOn(uploadService, 'isUploading').and.returnValue(true);
|
||||||
|
const documentListReloadSpy = spyOn(component.documentList, 'reloadWithoutResettingSelection');
|
||||||
|
|
||||||
|
const fakeFileModels = [new FileModel(<File> { name: 'fake-name', size: 100 }), new FileModel(<File> { name: 'fake-name-2', size: 200 })];
|
||||||
|
const fileUploadCompleteEvent = new FileUploadCompleteEvent(fakeFileModels[0], 1, fakeFileModels[0], 0);
|
||||||
|
uploadService.fileUploadComplete.next(fileUploadCompleteEvent);
|
||||||
|
|
||||||
|
tick(500);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.currentUploadBatch).toEqual([fileUploadCompleteEvent.data]);
|
||||||
|
expect(component.preselectedNodes).toEqual([]);
|
||||||
|
expect(documentListReloadSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
isUploadingSpy.and.returnValue(false);
|
||||||
|
|
||||||
|
const secondFileUploadCompleteEvent = new FileUploadCompleteEvent(fakeFileModels[1], 2, fakeFileModels[1], 0);
|
||||||
|
uploadService.fileUploadComplete.next(secondFileUploadCompleteEvent);
|
||||||
|
tick(500);
|
||||||
|
|
||||||
|
expect(component.currentUploadBatch).toEqual([]);
|
||||||
|
expect(component.preselectedNodes).toEqual([fileUploadCompleteEvent.data, secondFileUploadCompleteEvent.data]);
|
||||||
|
expect(documentListReloadSpy).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should call document list to unselect the row of the deleted upload', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const documentListUnselectRowSpy = spyOn(component.documentList, 'unselectRowFromNodeId');
|
||||||
|
const documentListReloadSpy = spyOn(component.documentList, 'reloadWithoutResettingSelection');
|
||||||
const fakeFileModel = new FileModel(<File> { name: 'fake-name', size: 10000000 });
|
const fakeFileModel = new FileModel(<File> { name: 'fake-name', size: 10000000 });
|
||||||
const fakeNodes = [<Node> { id: 'fakeNodeId' }, <Node> { id: 'fakeNodeId2' }];
|
const fakeNodes = [<Node> { id: 'fakeNodeId' }, <Node> { id: 'fakeNodeId2' }];
|
||||||
|
|
||||||
fakeFileModel.data = { entry: fakeNodes[0] };
|
fakeFileModel.data = { entry: fakeNodes[0] };
|
||||||
fakeFileModel.status = FileUploadStatus.Deleted;
|
fakeFileModel.status = FileUploadStatus.Deleted;
|
||||||
component._chosenNode = [...fakeNodes];
|
|
||||||
uploadService.cancelUpload(fakeFileModel);
|
uploadService.cancelUpload(fakeFileModel);
|
||||||
|
|
||||||
expect(selectSpy).toHaveBeenCalledWith([fakeNodes[1]]);
|
expect(documentListUnselectRowSpy).toHaveBeenCalledWith(fakeNodes[0].id);
|
||||||
expect(component._chosenNode).toEqual([fakeNodes[1]]);
|
expect(documentListReloadSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return only the last uploaded node to become preselected when the selection mode is single', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const fakeNodes = [new NodeEntry({ id: 'fakeNode1' }), new NodeEntry({ id: 'fakeNode2' })];
|
||||||
|
component.currentUploadBatch = fakeNodes;
|
||||||
|
component.selectionMode = 'single';
|
||||||
|
|
||||||
|
expect(component.getPreselectNodesBasedOnSelectionMode()).toEqual([fakeNodes[1]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all the uploaded nodes to become preselected when the selection mode is multiple', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const fakeNodes = [new NodeEntry({ id: 'fakeNode1' }), new NodeEntry({ id: 'fakeNode2' })];
|
||||||
|
component.currentUploadBatch = fakeNodes;
|
||||||
|
component.selectionMode = 'multiple';
|
||||||
|
|
||||||
|
expect(component.getPreselectNodesBasedOnSelectionMode()).toEqual(fakeNodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty array when no files are uploaded', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.currentUploadBatch = [];
|
||||||
|
|
||||||
|
expect(component.getPreselectNodesBasedOnSelectionMode()).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -36,7 +36,6 @@ import {
|
|||||||
UploadService,
|
UploadService,
|
||||||
FileUploadCompleteEvent,
|
FileUploadCompleteEvent,
|
||||||
FileUploadDeleteEvent,
|
FileUploadDeleteEvent,
|
||||||
FileModel,
|
|
||||||
AppConfigService,
|
AppConfigService,
|
||||||
DataSorting
|
DataSorting
|
||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
@@ -45,7 +44,7 @@ import { Node, NodePaging, Pagination, SiteEntry, SitePaging, NodeEntry, QueryBo
|
|||||||
import { DocumentListComponent } from '../document-list/components/document-list.component';
|
import { DocumentListComponent } from '../document-list/components/document-list.component';
|
||||||
import { RowFilter } from '../document-list/data/row-filter.model';
|
import { RowFilter } from '../document-list/data/row-filter.model';
|
||||||
import { ImageResolver } from '../document-list/data/image-resolver.model';
|
import { ImageResolver } from '../document-list/data/image-resolver.model';
|
||||||
import { debounceTime, takeUntil, scan } from 'rxjs/operators';
|
import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||||
import { CustomResourcesService } from '../document-list/services/custom-resources.service';
|
import { CustomResourcesService } from '../document-list/services/custom-resources.service';
|
||||||
import { NodeEntryEvent, ShareDataRow } from '../document-list';
|
import { NodeEntryEvent, ShareDataRow } from '../document-list';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
@@ -253,6 +252,8 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
target: PaginatedComponent;
|
target: PaginatedComponent;
|
||||||
preselectedNodes: NodeEntry[] = [];
|
preselectedNodes: NodeEntry[] = [];
|
||||||
|
currentUploadBatch: NodeEntry[] = [];
|
||||||
|
|
||||||
sorting: string[] | DataSorting;
|
sorting: string[] | DataSorting;
|
||||||
|
|
||||||
searchPanelExpanded: boolean = false;
|
searchPanelExpanded: boolean = false;
|
||||||
@@ -359,36 +360,29 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
private onFileUploadEvent() {
|
private onFileUploadEvent() {
|
||||||
this.uploadService.fileUploadComplete
|
this.uploadService.fileUploadComplete
|
||||||
.pipe(
|
.pipe(
|
||||||
debounceTime(500),
|
debounceTime(500),
|
||||||
scan((files, currentFile) => [...files, currentFile], []),
|
takeUntil(this.onDestroy$)
|
||||||
takeUntil(this.onDestroy$)
|
)
|
||||||
)
|
.subscribe((fileUploadEvent: FileUploadCompleteEvent) => {
|
||||||
.subscribe((uploadedFiles: FileUploadCompleteEvent[]) => {
|
this.currentUploadBatch.push(fileUploadEvent.data);
|
||||||
this.preselectedNodes = this.getPreselectNodesBasedOnSelectionMode(uploadedFiles);
|
if (!this.uploadService.isUploading()) {
|
||||||
this.documentList.reload();
|
this.preselectedNodes = this.getPreselectNodesBasedOnSelectionMode();
|
||||||
});
|
this.currentUploadBatch = [];
|
||||||
|
this.documentList.reloadWithoutResettingSelection();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFileUploadDeletedEvent() {
|
private onFileUploadDeletedEvent() {
|
||||||
this.uploadService.fileUploadDeleted
|
this.uploadService.fileUploadDeleted
|
||||||
.pipe(takeUntil(this.onDestroy$))
|
.pipe(takeUntil(this.onDestroy$))
|
||||||
.subscribe((deletedFileEvent: FileUploadDeleteEvent) => {
|
.subscribe((deletedFileEvent: FileUploadDeleteEvent) => {
|
||||||
this.removeFromChosenNodes(deletedFileEvent.file);
|
this.documentList.unselectRowFromNodeId(deletedFileEvent.file.data.entry.id);
|
||||||
this.documentList.reload();
|
this.documentList.reloadWithoutResettingSelection();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeFromChosenNodes(file: FileModel) {
|
|
||||||
if (this.chosenNode) {
|
|
||||||
const fileIndex = this.chosenNode.findIndex((chosenNode: Node) => chosenNode.id === file.data.entry.id);
|
|
||||||
if (fileIndex !== -1) {
|
|
||||||
this._chosenNode.splice(fileIndex, 1);
|
|
||||||
this.select.next(this._chosenNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getStartSite() {
|
private getStartSite() {
|
||||||
this.nodesApiService.getNode(this.currentFolderId).subscribe((startNodeEntry) => {
|
this.nodesApiService.getNode(this.currentFolderId).subscribe((startNodeEntry) => {
|
||||||
this.startSiteGuid = this.sitesService.getSiteNameFromNodePath(startNodeEntry);
|
this.startSiteGuid = this.sitesService.getSiteNameFromNodePath(startNodeEntry);
|
||||||
@@ -462,6 +456,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
|
|||||||
this.infinitePaginationComponent.reset();
|
this.infinitePaginationComponent.reset();
|
||||||
}
|
}
|
||||||
this.folderIdToShow = null;
|
this.folderIdToShow = null;
|
||||||
|
this.preselectedNodes = [];
|
||||||
this.loadingSearchResults = true;
|
this.loadingSearchResults = true;
|
||||||
this.addCorrespondingNodeIdsQuery();
|
this.addCorrespondingNodeIdsQuery();
|
||||||
this.resetChosenNode();
|
this.resetChosenNode();
|
||||||
@@ -630,14 +625,14 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
|
|||||||
return this.selectionMode === 'single';
|
return this.selectionMode === 'single';
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPreselectNodesBasedOnSelectionMode(uploadedFiles: FileUploadCompleteEvent[]): NodeEntry[] {
|
getPreselectNodesBasedOnSelectionMode(): NodeEntry[] {
|
||||||
let selectedNodes: NodeEntry[] = [];
|
let selectedNodes: NodeEntry[] = [];
|
||||||
|
|
||||||
if (uploadedFiles && uploadedFiles.length > 0 ) {
|
if (this.currentUploadBatch?.length) {
|
||||||
if (this.isSingleSelectionMode()) {
|
if (this.isSingleSelectionMode()) {
|
||||||
selectedNodes = [...[uploadedFiles[uploadedFiles.length - 1]].map((uploadedFile) => uploadedFile.data)];
|
selectedNodes = [this.currentUploadBatch[this.currentUploadBatch.length - 1]];
|
||||||
} else {
|
} else {
|
||||||
selectedNodes = [...uploadedFiles.map((uploadedFile) => uploadedFile.data)];
|
selectedNodes = this.currentUploadBatch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange, QueryList, Component, ViewChild } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange, QueryList, Component, ViewChild, SimpleChanges } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||||
import {
|
import {
|
||||||
setupTestBed,
|
setupTestBed,
|
||||||
@@ -32,13 +32,17 @@ import {
|
|||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
import { Subject, of, throwError } from 'rxjs';
|
import { Subject, of, throwError } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
FileNode, FolderNode,
|
FileNode,
|
||||||
|
FolderNode,
|
||||||
fakeNodeAnswerWithNOEntries,
|
fakeNodeAnswerWithNOEntries,
|
||||||
fakeNodeWithNoPermission,
|
fakeNodeWithNoPermission,
|
||||||
fakeGetSitesAnswer,
|
fakeGetSitesAnswer,
|
||||||
fakeGetSiteMembership,
|
fakeGetSiteMembership,
|
||||||
mockPreselectedNodes,
|
mockPreselectedNodes,
|
||||||
mockNodePagingWithPreselectedNodes
|
mockNodePagingWithPreselectedNodes,
|
||||||
|
mockNode1,
|
||||||
|
mockNode2,
|
||||||
|
mockNode3
|
||||||
} from '../../mock';
|
} from '../../mock';
|
||||||
import { ContentActionModel } from '../models/content-action.model';
|
import { ContentActionModel } from '../models/content-action.model';
|
||||||
import { NodeMinimal, NodeMinimalEntry, NodePaging } from '../models/document-library.model';
|
import { NodeMinimal, NodeMinimalEntry, NodePaging } from '../models/document-library.model';
|
||||||
@@ -236,6 +240,47 @@ describe('DocumentList', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not reset the selection when preselectNodes input changes', () => {
|
||||||
|
const resetSelectionSpy = spyOn(documentList, 'resetSelection').and.callThrough();
|
||||||
|
documentList.selection = [{ entry: mockNode3 }];
|
||||||
|
const changes: SimpleChanges = {
|
||||||
|
'preselectNodes': {
|
||||||
|
previousValue: undefined,
|
||||||
|
currentValue: mockPreselectedNodes,
|
||||||
|
firstChange: true,
|
||||||
|
isFirstChange(): boolean { return this.firstChange; }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
documentList.ngOnChanges(changes);
|
||||||
|
|
||||||
|
expect(resetSelectionSpy).not.toHaveBeenCalled();
|
||||||
|
expect(documentList.selection).toEqual([{ entry: mockNode3 }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset the selection for every change other than preselectNodes', () => {
|
||||||
|
const resetSelectionSpy = spyOn(documentList, 'resetSelection').and.callThrough();
|
||||||
|
documentList.selection = [{ entry: mockNode3 }];
|
||||||
|
const changes: SimpleChanges = {
|
||||||
|
'mockChange': {
|
||||||
|
previousValue: undefined,
|
||||||
|
currentValue: ['mockChangeValue'],
|
||||||
|
firstChange: true,
|
||||||
|
isFirstChange(): boolean { return this.firstChange; }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
documentList.ngOnChanges(changes);
|
||||||
|
|
||||||
|
expect(resetSelectionSpy).toHaveBeenCalled();
|
||||||
|
expect(documentList.selection).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reloadWithoutResettingSelection not reset the selection', () => {
|
||||||
|
documentList.selection = [{ entry: mockNode3 }];
|
||||||
|
documentList.reloadWithoutResettingSelection();
|
||||||
|
|
||||||
|
expect(documentList.selection).toEqual([{ entry: mockNode3 }]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should update schema if columns change', fakeAsync(() => {
|
it('should update schema if columns change', fakeAsync(() => {
|
||||||
documentList.columnList = new DataColumnListComponent();
|
documentList.columnList = new DataColumnListComponent();
|
||||||
documentList.columnList.columns = new QueryList<DataColumnComponent>();
|
documentList.columnList.columns = new QueryList<DataColumnComponent>();
|
||||||
@@ -1532,23 +1577,7 @@ describe('DocumentList', () => {
|
|||||||
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue(`assets/images/ft_ic_created.svg`);
|
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue(`assets/images/ft_ic_created.svg`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should able to emit nodeSelected event with preselectedNodes on the reload', async () => {
|
it('should return only the first node of the preselectedNodes when selection mode is single', async () => {
|
||||||
const nodeSelectedSpy = spyOn(documentList.nodeSelected, 'emit');
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
documentList.node = mockNodePagingWithPreselectedNodes;
|
|
||||||
documentList.preselectNodes = mockPreselectedNodes;
|
|
||||||
documentList.reload();
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(documentList.preselectNodes.length).toBe(2);
|
|
||||||
expect(nodeSelectedSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to select first node from the preselectedNodes when selectionMode set to single', async () => {
|
|
||||||
documentList.selectionMode = 'single';
|
documentList.selectionMode = 'single';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
@@ -1563,7 +1592,7 @@ describe('DocumentList', () => {
|
|||||||
expect(documentList.getPreselectedNodesBasedOnSelectionMode().length).toBe(1);
|
expect(documentList.getPreselectedNodesBasedOnSelectionMode().length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to select all preselectedNodes when selectionMode set to multiple', async () => {
|
it('should return all the preselectedNodes when selection mode is multiple', async () => {
|
||||||
documentList.selectionMode = 'multiple';
|
documentList.selectionMode = 'multiple';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
@@ -1581,11 +1610,11 @@ describe('DocumentList', () => {
|
|||||||
it('should call the datatable select row method for each preselected node', async () => {
|
it('should call the datatable select row method for each preselected node', async () => {
|
||||||
const datatableSelectRowSpy = spyOn(documentList.dataTable, 'selectRow');
|
const datatableSelectRowSpy = spyOn(documentList.dataTable, 'selectRow');
|
||||||
const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)];
|
const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)];
|
||||||
spyOn(documentList.data, 'hasPreselectedRows').and.returnValue(true);
|
|
||||||
spyOn(documentList.data, 'getPreselectedRows').and.returnValue(fakeDatatableRows);
|
|
||||||
|
|
||||||
documentList.selectionMode = 'multiple';
|
spyOn(documentList, 'preselectRowsOfPreselectedNodes');
|
||||||
|
documentList.preselectedRows = fakeDatatableRows;
|
||||||
documentList.preselectNodes = mockPreselectedNodes;
|
documentList.preselectNodes = mockPreselectedNodes;
|
||||||
|
documentList.selectionMode = 'multiple';
|
||||||
documentList.onPreselectNodes();
|
documentList.onPreselectNodes();
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -1608,6 +1637,165 @@ describe('DocumentList', () => {
|
|||||||
|
|
||||||
expect(nodeSelectedSpy).not.toHaveBeenCalled();
|
expect(nodeSelectedSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return only the first preselected row when selection mode is single', () => {
|
||||||
|
documentList.selectionMode = 'single';
|
||||||
|
const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)];
|
||||||
|
documentList.preselectedRows = fakeDatatableRows;
|
||||||
|
const preselectedRows = documentList.getPreselectedRowsBasedOnSelectionMode();
|
||||||
|
|
||||||
|
expect(preselectedRows.length).toEqual(1);
|
||||||
|
expect(preselectedRows[0]).toEqual(fakeDatatableRows[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all the preselected rows when selection mode is multiple', () => {
|
||||||
|
documentList.selectionMode = 'multiple';
|
||||||
|
const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)];
|
||||||
|
documentList.preselectedRows = fakeDatatableRows;
|
||||||
|
const preselectedRows = documentList.getPreselectedRowsBasedOnSelectionMode();
|
||||||
|
|
||||||
|
expect(preselectedRows.length).toEqual(2);
|
||||||
|
expect(preselectedRows).toEqual(fakeDatatableRows);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty array when there are no preselected rows', () => {
|
||||||
|
documentList.preselectedRows = undefined;
|
||||||
|
const preselectedRows = documentList.getPreselectedRowsBasedOnSelectionMode();
|
||||||
|
|
||||||
|
expect(preselectedRows).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should the combined selection be only the first preselected row when selection mode is single', () => {
|
||||||
|
const getSelectionFromAdapterSpy = spyOn(documentList.data, 'getSelectedRows');
|
||||||
|
const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)];
|
||||||
|
documentList.preselectedRows = fakeDatatableRows;
|
||||||
|
documentList.selectionMode = 'single';
|
||||||
|
const selection = documentList.getSelectionBasedOnSelectionMode();
|
||||||
|
|
||||||
|
expect(selection.length).toEqual(1);
|
||||||
|
expect(selection[0]).toEqual(fakeDatatableRows[0]);
|
||||||
|
expect(getSelectionFromAdapterSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the selection from the adapter when selection mode is multiple', () => {
|
||||||
|
const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)];
|
||||||
|
fakeDatatableRows[0].isSelected = true;
|
||||||
|
fakeDatatableRows[1].isSelected = false;
|
||||||
|
documentList.data.setRows(fakeDatatableRows);
|
||||||
|
|
||||||
|
const getSelectionFromAdapterSpy = spyOn(documentList.data, 'getSelectedRows').and.callThrough();
|
||||||
|
documentList.selectionMode = 'multiple';
|
||||||
|
documentList.preselectedRows = fakeDatatableRows;
|
||||||
|
const selection = documentList.getSelectionBasedOnSelectionMode();
|
||||||
|
|
||||||
|
expect(getSelectionFromAdapterSpy).toHaveBeenCalled();
|
||||||
|
expect(selection.length).toEqual(1);
|
||||||
|
expect(selection[0]).toEqual(fakeDatatableRows[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve the existing selection when selection mode is multiple', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
documentList.node = mockNodePagingWithPreselectedNodes;
|
||||||
|
documentList.selection = [{ entry: mockNode1 }, { entry: mockNode2 }];
|
||||||
|
documentList.selectionMode = 'multiple';
|
||||||
|
documentList.reloadWithoutResettingSelection();
|
||||||
|
const selectedRows = documentList.data.getSelectedRows();
|
||||||
|
|
||||||
|
expect(selectedRows.length).toEqual(2);
|
||||||
|
expect(selectedRows[0].id).toEqual(mockNode1.id);
|
||||||
|
expect(selectedRows[0].isSelected).toEqual(true);
|
||||||
|
expect(selectedRows[1].id).toEqual(mockNode2.id);
|
||||||
|
expect(selectedRows[1].isSelected).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not preserve the existing selection when selection mode is single', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
documentList.node = mockNodePagingWithPreselectedNodes;
|
||||||
|
documentList.selection = [{ entry: mockNode1 }, { entry: mockNode2 }];
|
||||||
|
documentList.selectionMode = 'single';
|
||||||
|
documentList.reloadWithoutResettingSelection();
|
||||||
|
const selectedRows = documentList.data.getSelectedRows();
|
||||||
|
|
||||||
|
expect(selectedRows.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unselect the row from the node id', () => {
|
||||||
|
const datatableSelectRowSpy = spyOn(documentList.dataTable, 'selectRow');
|
||||||
|
const getRowByNodeIdSpy = spyOn(documentList.data, 'getRowByNodeId').and.callThrough();
|
||||||
|
const onNodeUnselectSpy = spyOn(documentList, 'onNodeUnselect');
|
||||||
|
const getSelectionSpy = spyOn(documentList, 'getSelectionBasedOnSelectionMode').and.callThrough();
|
||||||
|
|
||||||
|
const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)];
|
||||||
|
fakeDatatableRows[0].isSelected = true;
|
||||||
|
documentList.data.setRows(fakeDatatableRows);
|
||||||
|
let selectedRows = documentList.data.getSelectedRows();
|
||||||
|
|
||||||
|
expect(selectedRows.length).toEqual(1);
|
||||||
|
|
||||||
|
documentList.unselectRowFromNodeId(mockPreselectedNodes[0].entry.id);
|
||||||
|
selectedRows = documentList.data.getSelectedRows();
|
||||||
|
|
||||||
|
expect(selectedRows).toEqual([]);
|
||||||
|
expect(getSelectionSpy).toHaveBeenCalled();
|
||||||
|
expect(getRowByNodeIdSpy).toHaveBeenCalledWith(mockPreselectedNodes[0].entry.id);
|
||||||
|
expect(datatableSelectRowSpy).toHaveBeenCalledWith(fakeDatatableRows[0], false);
|
||||||
|
expect(onNodeUnselectSpy).toHaveBeenCalledWith({ row: undefined, selection: <ShareDataRow[]> selectedRows });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preselect the rows of the preselected nodes', () => {
|
||||||
|
const getRowByNodeIdSpy = spyOn(documentList.data, 'getRowByNodeId').and.callThrough();
|
||||||
|
const getPreselectedNodesSpy = spyOn(documentList, 'getPreselectedNodesBasedOnSelectionMode').and.callThrough();
|
||||||
|
|
||||||
|
const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)];
|
||||||
|
documentList.data.setRows(fakeDatatableRows);
|
||||||
|
|
||||||
|
documentList.selectionMode = 'multiple';
|
||||||
|
documentList.preselectNodes = [...mockPreselectedNodes, { entry: mockNode3 }];
|
||||||
|
|
||||||
|
documentList.preselectRowsOfPreselectedNodes();
|
||||||
|
const selectedRows = documentList.data.getSelectedRows();
|
||||||
|
|
||||||
|
expect(getPreselectedNodesSpy).toHaveBeenCalled();
|
||||||
|
expect(selectedRows.length).toEqual(2);
|
||||||
|
expect(selectedRows[0].isSelected).toEqual(true);
|
||||||
|
expect(selectedRows[1].isSelected).toEqual(true);
|
||||||
|
|
||||||
|
expect(documentList.preselectedRows.length).toEqual(2);
|
||||||
|
expect(documentList.preselectedRows).toEqual(fakeDatatableRows);
|
||||||
|
|
||||||
|
expect(getRowByNodeIdSpy).toHaveBeenCalledWith(fakeDatatableRows[0].id);
|
||||||
|
expect(getRowByNodeIdSpy).toHaveBeenCalledWith(fakeDatatableRows[1].id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select the rows of the preselected nodes and emit the new combined selection', () => {
|
||||||
|
const hasPreselectedNodesSpy = spyOn(documentList, 'hasPreselectedNodes').and.callThrough();
|
||||||
|
const preselectRowsOfPreselectedNodesSpy = spyOn(documentList, 'preselectRowsOfPreselectedNodes').and.callThrough();
|
||||||
|
const getPreselectedRowsBasedOnSelectionModeSpy = spyOn(documentList, 'getPreselectedRowsBasedOnSelectionMode').and.callThrough();
|
||||||
|
const onNodeSelectSpy = spyOn(documentList, 'onNodeSelect').and.callThrough();
|
||||||
|
|
||||||
|
const fakeDatatableRows = [
|
||||||
|
new ShareDataRow(mockPreselectedNodes[0], contentService, null),
|
||||||
|
new ShareDataRow(mockPreselectedNodes[1], contentService, null),
|
||||||
|
new ShareDataRow({ entry: mockNode3 }, contentService, null)
|
||||||
|
];
|
||||||
|
fakeDatatableRows[2].isSelected = true;
|
||||||
|
documentList.data.setRows(fakeDatatableRows);
|
||||||
|
|
||||||
|
documentList.selection = [{ entry: mockNode3 }];
|
||||||
|
documentList.selectionMode = 'multiple';
|
||||||
|
documentList.preselectNodes = mockPreselectedNodes;
|
||||||
|
documentList.onPreselectNodes();
|
||||||
|
const selectedRows = documentList.data.getSelectedRows();
|
||||||
|
|
||||||
|
expect(hasPreselectedNodesSpy).toHaveBeenCalled();
|
||||||
|
expect(preselectRowsOfPreselectedNodesSpy).toHaveBeenCalled();
|
||||||
|
expect(getPreselectedRowsBasedOnSelectionModeSpy).toHaveBeenCalled();
|
||||||
|
expect(selectedRows.length).toEqual(3);
|
||||||
|
expect(selectedRows[0].id).toEqual(mockNode1.id);
|
||||||
|
expect(selectedRows[1].id).toEqual(mockNode2.id);
|
||||||
|
expect(selectedRows[2].id).toEqual(mockNode3.id);
|
||||||
|
expect(onNodeSelectSpy).toHaveBeenCalledWith({ row: undefined, selection: <ShareDataRow[]> selectedRows });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -44,7 +44,8 @@ import {
|
|||||||
RequestPaginationModel,
|
RequestPaginationModel,
|
||||||
AlfrescoApiService,
|
AlfrescoApiService,
|
||||||
UserPreferenceValues,
|
UserPreferenceValues,
|
||||||
LockService
|
LockService,
|
||||||
|
DataRow
|
||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
|
|
||||||
import { Node, NodeEntry, NodePaging, Pagination } from '@alfresco/js-api';
|
import { Node, NodeEntry, NodePaging, Pagination } from '@alfresco/js-api';
|
||||||
@@ -329,6 +330,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
$folderNode: Subject<Node> = new Subject<Node>();
|
$folderNode: Subject<Node> = new Subject<Node>();
|
||||||
allowFiltering: boolean = true;
|
allowFiltering: boolean = true;
|
||||||
orderBy: string[] = null;
|
orderBy: string[] = null;
|
||||||
|
preselectedRows: DataRow[] = [];
|
||||||
|
|
||||||
// @deprecated 3.0.0
|
// @deprecated 3.0.0
|
||||||
folderNode: Node;
|
folderNode: Node;
|
||||||
@@ -455,7 +457,9 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
this.resetSelection();
|
if (!changes['preselectNodes']) {
|
||||||
|
this.resetSelection();
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(this.sorting)) {
|
if (Array.isArray(this.sorting)) {
|
||||||
const [key, direction] = this.sorting;
|
const [key, direction] = this.sorting;
|
||||||
@@ -487,7 +491,8 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
if (this.data) {
|
if (this.data) {
|
||||||
if (changes.node && changes.node.currentValue) {
|
if (changes.node && changes.node.currentValue) {
|
||||||
const merge = this._pagination ? this._pagination.merge : false;
|
const merge = this._pagination ? this._pagination.merge : false;
|
||||||
this.data.loadPage(changes.node.currentValue, merge, null, this.getPreselectedNodesBasedOnSelectionMode());
|
this.data.loadPage(changes.node.currentValue, merge, null);
|
||||||
|
this.preserveExistingSelection();
|
||||||
this.onPreselectNodes();
|
this.onPreselectNodes();
|
||||||
this.onDataReady(changes.node.currentValue);
|
this.onDataReady(changes.node.currentValue);
|
||||||
} else if (changes.imageResolver) {
|
} else if (changes.imageResolver) {
|
||||||
@@ -499,19 +504,24 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
reload() {
|
reload() {
|
||||||
this.ngZone.run(() => {
|
this.ngZone.run(() => {
|
||||||
this.resetSelection();
|
this.resetSelection();
|
||||||
if (this.node) {
|
this.reloadWithoutResettingSelection();
|
||||||
if (this.data) {
|
|
||||||
this.data.loadPage(this.node, this._pagination.merge, null, this.getPreselectedNodesBasedOnSelectionMode());
|
|
||||||
}
|
|
||||||
this.onPreselectNodes();
|
|
||||||
this.syncPagination();
|
|
||||||
this.onDataReady(this.node);
|
|
||||||
} else {
|
|
||||||
this.loadFolder();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reloadWithoutResettingSelection() {
|
||||||
|
if (this.node) {
|
||||||
|
if (this.data) {
|
||||||
|
this.data.loadPage(this.node, this._pagination.merge, null);
|
||||||
|
this.preserveExistingSelection();
|
||||||
|
}
|
||||||
|
this.onPreselectNodes();
|
||||||
|
this.syncPagination();
|
||||||
|
this.onDataReady(this.node);
|
||||||
|
} else {
|
||||||
|
this.loadFolder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contextActionCallback(action) {
|
contextActionCallback(action) {
|
||||||
if (action) {
|
if (action) {
|
||||||
this.executeContentAction(action.node, action.model);
|
this.executeContentAction(action.node, action.model);
|
||||||
@@ -694,7 +704,8 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
onPageLoaded(nodePaging: NodePaging) {
|
onPageLoaded(nodePaging: NodePaging) {
|
||||||
if (nodePaging) {
|
if (nodePaging) {
|
||||||
if (this.data) {
|
if (this.data) {
|
||||||
this.data.loadPage(nodePaging, this._pagination.merge, this.allowDropFiles, this.getPreselectedNodesBasedOnSelectionMode());
|
this.data.loadPage(nodePaging, this._pagination.merge, this.allowDropFiles);
|
||||||
|
this.preserveExistingSelection();
|
||||||
}
|
}
|
||||||
this.onPreselectNodes();
|
this.onPreselectNodes();
|
||||||
this.setLoadingState(false);
|
this.setLoadingState(false);
|
||||||
@@ -800,7 +811,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
}
|
}
|
||||||
|
|
||||||
onNodeSelect(event: { row: ShareDataRow, selection: Array<ShareDataRow> }) {
|
onNodeSelect(event: { row: ShareDataRow, selection: Array<ShareDataRow> }) {
|
||||||
this.selection = event.selection.filter(entry => entry.node).map((entry) => entry.node);
|
this.selection = event.selection.map((entry) => entry.node);
|
||||||
const domEvent = new CustomEvent('node-select', {
|
const domEvent = new CustomEvent('node-select', {
|
||||||
detail: {
|
detail: {
|
||||||
node: event.row ? event.row.node : null,
|
node: event.row ? event.row.node : null,
|
||||||
@@ -923,23 +934,74 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
return this.hasPreselectedNodes() ? (this.isSingleSelectionMode() ? [this.preselectNodes[0]] : this.preselectNodes) : [];
|
return this.hasPreselectedNodes() ? (this.isSingleSelectionMode() ? [this.preselectNodes[0]] : this.preselectNodes) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
onPreselectNodes() {
|
getPreselectedRowsBasedOnSelectionMode(): DataRow[] {
|
||||||
if (this.data?.hasPreselectedRows()) {
|
return this.hasPreselectedRows() ? (this.isSingleSelectionMode() ? [this.preselectedRows[0]] : this.preselectedRows) : [];
|
||||||
const preselectedNodes = [...this.isSingleSelectionMode() ? [this.data.getPreselectedRows()[0]] : this.data.getPreselectedRows()];
|
}
|
||||||
const selectedNodes = [...this.selection, ...preselectedNodes];
|
|
||||||
|
|
||||||
for (const node of preselectedNodes) {
|
getSelectionBasedOnSelectionMode(): DataRow[] {
|
||||||
|
return this.hasPreselectedRows() ? (this.isSingleSelectionMode() ? [this.preselectedRows[0]] : this.data.getSelectedRows()) : this.data.getSelectedRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPreselectNodes() {
|
||||||
|
if (this.hasPreselectedNodes()) {
|
||||||
|
this.preselectRowsOfPreselectedNodes();
|
||||||
|
const preselectedRows = this.getPreselectedRowsBasedOnSelectionMode();
|
||||||
|
const selectedNodes = this.data.getSelectedRows();
|
||||||
|
|
||||||
|
for (const node of preselectedRows) {
|
||||||
this.dataTable.selectRow(node, true);
|
this.dataTable.selectRow(node, true);
|
||||||
}
|
}
|
||||||
this.onNodeSelect({ row: undefined, selection: <ShareDataRow[]> selectedNodes });
|
this.onNodeSelect({ row: undefined, selection: <ShareDataRow[]> selectedNodes });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preserveExistingSelection() {
|
||||||
|
if (this.isMultipleSelectionMode()) {
|
||||||
|
for (const selection of this.selection) {
|
||||||
|
const rowOfSelection = this.data.getRowByNodeId(selection.entry.id);
|
||||||
|
if (rowOfSelection) {
|
||||||
|
rowOfSelection.isSelected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preselectRowsOfPreselectedNodes() {
|
||||||
|
this.preselectedRows = [];
|
||||||
|
const preselectedNodes = this.getPreselectedNodesBasedOnSelectionMode();
|
||||||
|
|
||||||
|
preselectedNodes.forEach((preselectedNode: NodeEntry) => {
|
||||||
|
const rowOfPreselectedNode = this.data.getRowByNodeId(preselectedNode.entry.id);
|
||||||
|
if (rowOfPreselectedNode) {
|
||||||
|
rowOfPreselectedNode.isSelected = true;
|
||||||
|
this.preselectedRows.push(rowOfPreselectedNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unselectRowFromNodeId(nodeId: string) {
|
||||||
|
const rowToUnselect = this.data.getRowByNodeId(nodeId);
|
||||||
|
if (rowToUnselect?.isSelected) {
|
||||||
|
rowToUnselect.isSelected = false;
|
||||||
|
this.dataTable.selectRow(rowToUnselect, false);
|
||||||
|
const selection = this.getSelectionBasedOnSelectionMode();
|
||||||
|
this.onNodeUnselect({ row: undefined, selection: <ShareDataRow[]> selection });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isSingleSelectionMode(): boolean {
|
isSingleSelectionMode(): boolean {
|
||||||
return this.selectionMode === 'single';
|
return this.selectionMode === 'single';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMultipleSelectionMode(): boolean {
|
||||||
|
return this.selectionMode === 'multiple';
|
||||||
|
}
|
||||||
|
|
||||||
hasPreselectedNodes(): boolean {
|
hasPreselectedNodes(): boolean {
|
||||||
return this.preselectNodes?.length > 0;
|
return this.preselectNodes?.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasPreselectedRows(): boolean {
|
||||||
|
return this.preselectedRows?.length > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ export class ShareDataRow implements DataRow {
|
|||||||
isSelected: boolean = false;
|
isSelected: boolean = false;
|
||||||
isDropTarget: boolean;
|
isDropTarget: boolean;
|
||||||
cssClass: string = '';
|
cssClass: string = '';
|
||||||
|
id: string;
|
||||||
|
|
||||||
get node(): NodeEntry {
|
get node(): NodeEntry {
|
||||||
return this.obj;
|
return this.obj;
|
||||||
@@ -50,6 +51,7 @@ export class ShareDataRow implements DataRow {
|
|||||||
if (permissionsStyle) {
|
if (permissionsStyle) {
|
||||||
this.cssClass = this.getPermissionClass(obj);
|
this.cssClass = this.getPermissionClass(obj);
|
||||||
}
|
}
|
||||||
|
this.id = this.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNodeTypeAndPermissions(nodeEntry: NodeEntry) {
|
checkNodeTypeAndPermissions(nodeEntry: NodeEntry) {
|
||||||
@@ -118,4 +120,8 @@ export class ShareDataRow implements DataRow {
|
|||||||
hasValue(key: string): boolean {
|
hasValue(key: string): boolean {
|
||||||
return this.getValue(key) !== undefined;
|
return this.getValue(key) !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getId(): string {
|
||||||
|
return this.obj.entry.id || undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataColumn, DataRow, DataSorting, ContentService, ThumbnailService, setupTestBed } from '@alfresco/adf-core';
|
import { DataColumn, DataRow, DataSorting, ContentService, ThumbnailService, setupTestBed } from '@alfresco/adf-core';
|
||||||
import { FileNode, FolderNode, SmartFolderNode, RuleFolderNode, LinkFolderNode, mockPreselectedNodes, mockNodePagingWithPreselectedNodes, mockNode2, fakeNodePaging, mockNode1 } from './../../mock';
|
import { FileNode, FolderNode, SmartFolderNode, RuleFolderNode, LinkFolderNode } from './../../mock';
|
||||||
import { ShareDataRow } from './share-data-row.model';
|
import { ShareDataRow } from './share-data-row.model';
|
||||||
import { ShareDataTableAdapter } from './share-datatable-adapter';
|
import { ShareDataTableAdapter } from './share-datatable-adapter';
|
||||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||||
@@ -480,42 +480,31 @@ describe('ShareDataTableAdapter', () => {
|
|||||||
|
|
||||||
expect(row.isDropTarget).toBeFalsy();
|
expect(row.isDropTarget).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('Preselect rows', () => {
|
it('should return all the selected rows', () => {
|
||||||
|
const file = new FileNode();
|
||||||
|
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, null);
|
||||||
|
const row1 = new ShareDataRow(file, contentService, null);
|
||||||
|
const row2 = new ShareDataRow(file, contentService, null);
|
||||||
|
const row3 = new ShareDataRow(file, contentService, null);
|
||||||
|
|
||||||
it('should set isSelected to be true for each preselectRow if the preselectedNodes are defined', () => {
|
row1.isSelected = true;
|
||||||
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
|
row2.isSelected = true;
|
||||||
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, mockPreselectedNodes);
|
adapter.setRows([row1, row2, row3]);
|
||||||
|
const selectedRows = adapter.getSelectedRows();
|
||||||
|
|
||||||
expect(adapter.getPreselectedRows().length).toBe(1);
|
expect(selectedRows.length).toEqual(2);
|
||||||
expect(adapter.getPreselectedRows()[0].isSelected).toBe(true);
|
expect(selectedRows).toEqual([row1, row2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set preselectedRows empty if preselectedNodes are undefined/empty', () => {
|
it('should return the row of the requested node id', () => {
|
||||||
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
|
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, null);
|
||||||
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, []);
|
const fakeFiles = [new FileNode('fake-file-1', 'text/plain', 'fake-node-id-1'), new FileNode('fake-file-2', 'text/plain', 'fake-node-id-2')];
|
||||||
|
const fakeShareDataRows = [new ShareDataRow(fakeFiles[0], contentService, null), new ShareDataRow(fakeFiles[1], contentService, null)];
|
||||||
|
adapter.setRows(fakeShareDataRows);
|
||||||
|
|
||||||
expect(adapter.getPreselectedRows().length).toBe(0);
|
expect(adapter.getRowByNodeId('fake-node-id-1')).toEqual(fakeShareDataRows[0]);
|
||||||
});
|
expect(adapter.getRowByNodeId('fake-node-id-2')).toEqual(fakeShareDataRows[1]);
|
||||||
|
|
||||||
it('should set preselectedRows empty if preselectedNodes are not found in the list', () => {
|
|
||||||
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
|
|
||||||
mockNode2.id = 'mock-file-id';
|
|
||||||
const preselectedNode = [ { entry: mockNode2 }];
|
|
||||||
adapter.loadPage(fakeNodePaging, null, null, preselectedNode);
|
|
||||||
|
|
||||||
expect(adapter.getPreselectedRows().length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should preselected rows contain only the valid rows that exist in the datatable', () => {
|
|
||||||
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
|
|
||||||
const nonExistingEntry = {...mockNode1};
|
|
||||||
nonExistingEntry.id = 'non-existing-entry-id';
|
|
||||||
const preselectedNodes = [{ entry: nonExistingEntry }, { entry: mockNode1 }, { entry: mockNode2 }];
|
|
||||||
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, preselectedNodes);
|
|
||||||
|
|
||||||
expect(adapter.getPreselectedRows().length).toBe(2);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -45,7 +45,6 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
permissionsStyle: PermissionStyleModel[];
|
permissionsStyle: PermissionStyleModel[];
|
||||||
selectedRow: DataRow;
|
selectedRow: DataRow;
|
||||||
allowDropFiles: boolean;
|
allowDropFiles: boolean;
|
||||||
preselectedRows: DataRow[] = [];
|
|
||||||
|
|
||||||
set sortingMode(value: string) {
|
set sortingMode(value: string) {
|
||||||
let newValue = (value || 'client').toLowerCase();
|
let newValue = (value || 'client').toLowerCase();
|
||||||
@@ -82,10 +81,6 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
this.sort();
|
this.sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
getPreselectedRows(): Array<DataRow> {
|
|
||||||
return this.preselectedRows;
|
|
||||||
}
|
|
||||||
|
|
||||||
getColumns(): Array<DataColumn> {
|
getColumns(): Array<DataColumn> {
|
||||||
return this.columns;
|
return this.columns;
|
||||||
}
|
}
|
||||||
@@ -250,7 +245,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadPage(nodePaging: NodePaging, merge: boolean = false, allowDropFiles?: boolean, preselectNodes: NodeEntry[] = []) {
|
public loadPage(nodePaging: NodePaging, merge: boolean = false, allowDropFiles?: boolean) {
|
||||||
let shareDataRows: ShareDataRow[] = [];
|
let shareDataRows: ShareDataRow[] = [];
|
||||||
if (allowDropFiles !== undefined) {
|
if (allowDropFiles !== undefined) {
|
||||||
this.allowDropFiles = allowDropFiles;
|
this.allowDropFiles = allowDropFiles;
|
||||||
@@ -258,8 +253,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
if (nodePaging?.list) {
|
if (nodePaging?.list) {
|
||||||
const nodeEntries: NodeEntry[] = nodePaging.list.entries;
|
const nodeEntries: NodeEntry[] = nodePaging.list.entries;
|
||||||
if (nodeEntries?.length) {
|
if (nodeEntries?.length) {
|
||||||
shareDataRows = nodeEntries.map((item) => new ShareDataRow(item, this.contentService, this.permissionsStyle,
|
shareDataRows = nodeEntries.map((item) => new ShareDataRow(item, this.contentService, this.permissionsStyle, this.thumbnailService, this.allowDropFiles));
|
||||||
this.thumbnailService, this.allowDropFiles));
|
|
||||||
|
|
||||||
if (this.filter) {
|
if (this.filter) {
|
||||||
shareDataRows = shareDataRows.filter(this.filter);
|
shareDataRows = shareDataRows.filter(this.filter);
|
||||||
@@ -297,27 +291,13 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
} else {
|
} else {
|
||||||
this.rows = shareDataRows;
|
this.rows = shareDataRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectRowsBasedOnGivenNodes(preselectNodes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectRowsBasedOnGivenNodes(preselectNodes: NodeEntry[]) {
|
getSelectedRows(): DataRow[] {
|
||||||
if (preselectNodes?.length) {
|
return this.rows.filter((row: DataRow) => row.isSelected);
|
||||||
this.rows = this.rows.map((row) => {
|
|
||||||
preselectNodes.map((preselectedNode) => {
|
|
||||||
if (row.obj.entry.id === preselectedNode.entry.id) {
|
|
||||||
row.isSelected = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.preselectedRows = [...this.rows.filter((res) => res.isSelected)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPreselectedRows(): boolean {
|
getRowByNodeId(nodeId: string): DataRow {
|
||||||
return this.preselectedRows?.length > 0;
|
return this.rows.find((row: DataRow) => row.node.entry.id === nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -33,10 +33,10 @@ export class PageNode extends NodePaging {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FileNode extends NodeMinimalEntry {
|
export class FileNode extends NodeMinimalEntry {
|
||||||
constructor(name?: string, mimeType?: string) {
|
constructor(name?: string, mimeType?: string, id?: string) {
|
||||||
super();
|
super();
|
||||||
this.entry = new NodeMinimal();
|
this.entry = new NodeMinimal();
|
||||||
this.entry.id = 'file-id';
|
this.entry.id = id || 'file-id';
|
||||||
this.entry.isFile = true;
|
this.entry.isFile = true;
|
||||||
this.entry.isFolder = false;
|
this.entry.isFolder = false;
|
||||||
this.entry.name = name;
|
this.entry.name = name;
|
||||||
|
@@ -308,7 +308,7 @@ export const mockPreselectedNodes: NodeEntry[] = [
|
|||||||
entry: mockNode1
|
entry: mockNode1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
entry: mockNode1
|
entry: mockNode2
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -40,6 +40,7 @@ class CustomColumnTemplateComponent {
|
|||||||
class FakeDataRow implements DataRow {
|
class FakeDataRow implements DataRow {
|
||||||
isDropTarget = false;
|
isDropTarget = false;
|
||||||
isSelected = true;
|
isSelected = true;
|
||||||
|
id?: string;
|
||||||
|
|
||||||
hasValue() {
|
hasValue() {
|
||||||
return true;
|
return true;
|
||||||
@@ -593,6 +594,45 @@ describe('DataTable', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should unselect the row searching it by row id, when row id is defined', () => {
|
||||||
|
const findSelectionByIdSpy = spyOn(dataTable, 'findSelectionById');
|
||||||
|
dataTable.data = new ObjectDataTableAdapter([],
|
||||||
|
[new ObjectDataColumn({ key: 'name' })]
|
||||||
|
);
|
||||||
|
|
||||||
|
const fakeDataRows = [new FakeDataRow(), new FakeDataRow()];
|
||||||
|
fakeDataRows[0].id = 'fakeRowId';
|
||||||
|
fakeDataRows[1].id = 'fakeRowId2';
|
||||||
|
|
||||||
|
dataTable.data.setRows(fakeDataRows);
|
||||||
|
dataTable.selection = [...fakeDataRows];
|
||||||
|
const indexOfSpy = spyOn(dataTable.selection, 'indexOf');
|
||||||
|
|
||||||
|
dataTable.selectRow(fakeDataRows[0], false);
|
||||||
|
|
||||||
|
expect(indexOfSpy).not.toHaveBeenCalled();
|
||||||
|
expect(findSelectionByIdSpy).toHaveBeenCalledWith(fakeDataRows[0].id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unselect the row by searching for the exact same reference of it (indexOf), when row id is not defined ', () => {
|
||||||
|
const findSelectionByIdSpy = spyOn(dataTable, 'findSelectionById');
|
||||||
|
dataTable.data = new ObjectDataTableAdapter([],
|
||||||
|
[new ObjectDataColumn({ key: 'name' })]
|
||||||
|
);
|
||||||
|
|
||||||
|
const fakeDataRows = [new FakeDataRow(), new FakeDataRow()];
|
||||||
|
dataTable.data.setRows(fakeDataRows);
|
||||||
|
dataTable.selection = [...fakeDataRows];
|
||||||
|
const indexOfSpy = spyOn(dataTable.selection, 'indexOf').and.returnValue(0);
|
||||||
|
|
||||||
|
dataTable.selectRow(fakeDataRows[0], false);
|
||||||
|
|
||||||
|
expect(indexOfSpy).toHaveBeenCalled();
|
||||||
|
expect(findSelectionByIdSpy).not.toHaveBeenCalled();
|
||||||
|
expect(dataTable.selection.length).toEqual(1);
|
||||||
|
expect(dataTable.selection[0]).toEqual(fakeDataRows[1]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should select multiple rows with [multiple] selection mode and modifier key', (done) => {
|
it('should select multiple rows with [multiple] selection mode and modifier key', (done) => {
|
||||||
dataTable.selectionMode = 'multiple';
|
dataTable.selectionMode = 'multiple';
|
||||||
dataTable.data = new ObjectDataTableAdapter(
|
dataTable.data = new ObjectDataTableAdapter(
|
||||||
|
@@ -710,7 +710,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
|
|||||||
selectRow(row: DataRow, value: boolean) {
|
selectRow(row: DataRow, value: boolean) {
|
||||||
if (row) {
|
if (row) {
|
||||||
row.isSelected = value;
|
row.isSelected = value;
|
||||||
const idx = this.selection.indexOf(row);
|
const idx = row?.id ? this.findSelectionById(row.id) : this.selection.indexOf(row);
|
||||||
if (value) {
|
if (value) {
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
this.selection.push(row);
|
this.selection.push(row);
|
||||||
@@ -723,6 +723,10 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findSelectionById(id: string): number {
|
||||||
|
return this.selection.findIndex(selection => selection?.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
getCellTooltip(row: DataRow, col: DataColumn): string {
|
getCellTooltip(row: DataRow, col: DataColumn): string {
|
||||||
if (row && col && col.formatTooltip) {
|
if (row && col && col.formatTooltip) {
|
||||||
const result: string = col.formatTooltip(row, col);
|
const result: string = col.formatTooltip(row, col);
|
||||||
|
@@ -21,6 +21,7 @@ export interface DataRow {
|
|||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
isDropTarget?: boolean;
|
isDropTarget?: boolean;
|
||||||
cssClass?: string;
|
cssClass?: string;
|
||||||
|
id?: string;
|
||||||
|
|
||||||
hasValue(key: string): boolean;
|
hasValue(key: string): boolean;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user