diff --git a/demo-shell/src/app/app.module.ts b/demo-shell/src/app/app.module.ts
index 50899dc56c..9cc184f586 100644
--- a/demo-shell/src/app/app.module.ts
+++ b/demo-shell/src/app/app.module.ts
@@ -132,7 +132,7 @@ registerLocaleData(localeSv);
ContentModule.forRoot(),
InsightsModule.forRoot(),
ProcessModule.forRoot(),
- ProcessServicesCloudModule,
+ ProcessServicesCloudModule.forRoot(),
ExtensionsModule.forRoot(),
ThemePickerModule,
ChartsModule,
diff --git a/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.ts b/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.ts
index dd680a36cb..84336c708a 100644
--- a/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.ts
+++ b/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.ts
@@ -17,13 +17,17 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
-import { NotificationService, AppConfigService } from '@alfresco/adf-core';
+import { NotificationService, AppConfigService, FormRenderingService } from '@alfresco/adf-core';
import { CloudLayoutService } from './services/cloud-layout.service';
import { PreviewService } from '../../services/preview.service';
+import { CloudFormRenderingService } from '@alfresco/adf-process-services-cloud';
@Component({
templateUrl: './start-process-cloud-demo.component.html',
- styleUrls: ['./start-process-cloud-demo.component.scss']
+ styleUrls: ['./start-process-cloud-demo.component.scss'],
+ providers: [
+ { provide: FormRenderingService, useClass: CloudFormRenderingService }
+ ]
})
export class StartProcessCloudDemoComponent implements OnInit {
diff --git a/docs/content-services/components/content-node-selector-panel.component.md b/docs/content-services/components/content-node-selector-panel.component.md
index a0c0086a79..17c9422fe7 100644
--- a/docs/content-services/components/content-node-selector-panel.component.md
+++ b/docs/content-services/components/content-node-selector-panel.component.md
@@ -44,6 +44,7 @@ Opens a [Content Node Selector](content-node-selector.component.md) in its own
| showDropdownSiteList | `boolean` | | Toggle sites list dropdown rendering |
| showFilesInResult | `void` | | Shows the files and folders in the search result |
| showSearch | `boolean` | | Toggle search input rendering |
+| selectionMode | `string` | "single" | Row selection mode. Can be none, `single` or `multiple`. For `multiple` mode, you can use Cmd (macOS) or Ctrl (Win) modifier key to toggle selection for multiple rows. |
### Events
diff --git a/docs/content-services/components/document-list.component.md b/docs/content-services/components/document-list.component.md
index b20ef47027..3bd6f24b3d 100644
--- a/docs/content-services/components/document-list.component.md
+++ b/docs/content-services/components/document-list.component.md
@@ -94,6 +94,7 @@ Displays the documents from a repository.
| nodeDblClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntityEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the user double-clicks a list node |
| preview | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntityEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the user acts upon files with either single or double click (depends on `navigation-mode`). Useful for integration with the [Viewer component](../../core/components/viewer.component.md). |
| ready | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/NodePaging.md)`>` | Emitted when the Document List has loaded all items and is ready for use |
+| nodeSelected | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntryEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Returns the current node selected |
## Details
diff --git a/lib/content-services/src/lib/content-node-selector/content-node-dialog.service.ts b/lib/content-services/src/lib/content-node-selector/content-node-dialog.service.ts
index abd4323abf..6e3e8fd796 100644
--- a/lib/content-services/src/lib/content-node-selector/content-node-dialog.service.ts
+++ b/lib/content-services/src/lib/content-node-selector/content-node-dialog.service.ts
@@ -140,6 +140,7 @@ export class ContentNodeDialogService {
const data: ContentNodeSelectorComponentData = {
title: this.getTitleTranslation(action, contentEntry.name),
actionName: action,
+ selectionMode: 'single',
currentFolderId: contentEntry.parentId,
imageResolver: this.imageResolver.bind(this),
where: '(isFolder=true)',
@@ -182,6 +183,7 @@ export class ContentNodeDialogService {
const data: ContentNodeSelectorComponentData = {
title: this.getTitleTranslation(action, this.translation.instant('DROPDOWN.MY_FILES_OPTION')),
actionName: action,
+ selectionMode: 'single',
currentFolderId: contentEntry.id,
imageResolver: this.imageResolver.bind(this),
isSelectionValid: this.hasAllowableOperationsOnNodeFolder.bind(this),
@@ -209,6 +211,7 @@ export class ContentNodeDialogService {
const data: ContentNodeSelectorComponentData = {
title: this.getTitleTranslation(action, this.translation.instant('DROPDOWN.MY_FILES_OPTION')),
actionName: action,
+ selectionMode: 'single',
currentFolderId: contentEntry.id,
imageResolver: this.imageResolver.bind(this),
isSelectionValid: (entry: Node) => entry.isFile,
diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.html b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.html
index 91041822c8..3e90f3693c 100644
--- a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.html
+++ b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.html
@@ -1,4 +1,4 @@
-
+
diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.spec.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.spec.ts
index 417ad1b8dd..868441e18f 100644
--- a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.spec.ts
+++ b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.spec.ts
@@ -133,14 +133,14 @@ describe('ContentNodeSelectorComponent', () => {
});
it('should trigger the select event when selection has been made', (done) => {
- const expectedNode = {};
+ const expectedNode = { id: 'fakeid'};
component.select.subscribe((nodes) => {
expect(nodes.length).toBe(1);
expect(nodes[0]).toBe(expectedNode);
done();
});
- component.chosenNode = expectedNode;
+ component.chosenNode = [expectedNode];
});
it('should be able to filter out the exclude site content', () => {
@@ -291,7 +291,7 @@ describe('ContentNodeSelectorComponent', () => {
tick(debounceSearch);
const chosenNode = new Node({ path: { elements: ['one'] } });
- component.onNodeSelect({ detail: { node: { entry: chosenNode } } });
+ component.onCurrentSelection([ { entry: chosenNode } ]);
fixture.detectChanges();
tick(debounceSearch);
@@ -313,7 +313,7 @@ describe('ContentNodeSelectorComponent', () => {
fixture.detectChanges();
const chosenNode = { path: { elements: [] } };
- component.onNodeSelect({ detail: { node: { entry: chosenNode } } });
+ component.onCurrentSelection([ { entry: chosenNode } ]);
fixture.detectChanges();
tick(debounceSearch);
@@ -395,6 +395,7 @@ describe('ContentNodeSelectorComponent', () => {
describe('Search functionality', () => {
let getCorrespondingNodeIdsSpy;
+ const entry: Node = { id: 'fakeid'};
const defaultSearchOptions = (searchTerm, rootNodeId = undefined, skipCount = 0, showFiles = false) => {
@@ -472,7 +473,7 @@ describe('ContentNodeSelectorComponent', () => {
}));
it('should reset the currently chosen node in case of starting a new search', fakeAsync(() => {
- component.chosenNode = {};
+ component.chosenNode = [entry];
typeToSearchBox('kakarot');
tick(debounceSearch);
@@ -617,10 +618,11 @@ describe('ContentNodeSelectorComponent', () => {
}));
it('should clear the search field, nodes and chosenNode when clicking on the X (clear) icon', () => {
- component.chosenNode = {};
+ component.chosenNode = [entry];
+
component.nodePaging = {
list: {
- entries: [{ entry: component.chosenNode }]
+ entries: [{ entry }]
}
};
component.searchTerm = 'piccolo';
@@ -935,7 +937,7 @@ describe('ContentNodeSelectorComponent', () => {
describe('Chosen node', () => {
- const entry: Node = {};
+ const entry: Node = { id: 'fakeid'};
const nodePage: NodePaging = { list: {}, pagination: {} };
let hasAllowableOperations;
@@ -960,11 +962,10 @@ describe('ContentNodeSelectorComponent', () => {
component.select.subscribe((nodes) => {
expect(nodes).toBeDefined();
expect(nodes).not.toBeNull();
- expect(component.chosenNode).toBe(entry);
+ expect(component.chosenNode[0]).toBe(entry);
});
component.documentList.ready.emit(nodePage);
- fixture.detectChanges();
}));
it('should be null after selecting node without the necessary permissions', async(() => {
@@ -978,7 +979,6 @@ describe('ContentNodeSelectorComponent', () => {
});
component.documentList.ready.emit(nodePage);
- fixture.detectChanges();
}));
it('should NOT be null after clicking on a node (with the right permissions) in the list (onNodeSelect)', async(() => {
@@ -987,54 +987,48 @@ describe('ContentNodeSelectorComponent', () => {
component.select.subscribe((nodes) => {
expect(nodes).toBeDefined();
expect(nodes).not.toBeNull();
- expect(component.chosenNode).toBe(entry);
+ expect(component.chosenNode[0]).toBe(entry);
});
- component.onNodeSelect({ detail: { node: { entry } } });
- fixture.detectChanges();
+ component.onCurrentSelection([ { entry } ]);
}));
- it('should remain null when clicking on a node (with the WRONG permissions) in the list (onNodeSelect)', async(() => {
+ it('should remain empty when clicking on a node (with the WRONG permissions) in the list (onNodeSelect)', async(() => {
hasAllowableOperations = false;
component.select.subscribe((nodes) => {
expect(nodes).toBeDefined();
- expect(nodes).toBeNull();
- expect(component.chosenNode).toBeNull();
+ expect(nodes).toEqual([]);
+ expect(component.chosenNode).toEqual([]);
});
- component.onNodeSelect({ detail: { node: { entry } } });
- fixture.detectChanges();
+ component.onCurrentSelection([ { entry } ]);
}));
- it('should become null when clicking on a node (with the WRONG permissions) after previously selecting a right node', async(() => {
+ it('should become empty when clicking on a node (with the WRONG permissions) after previously selecting a right node', async(() => {
component.select.subscribe((nodes) => {
-
if (hasAllowableOperations) {
expect(nodes).toBeDefined();
expect(nodes).not.toBeNull();
- expect(component.chosenNode).not.toBeNull();
-
+ expect(component.chosenNode[0]).not.toBeNull();
} else {
expect(nodes).toBeDefined();
- expect(nodes).toBeNull();
- expect(component.chosenNode).toBeNull();
+ expect(nodes).toEqual([]);
+ expect(component.chosenNode).toEqual([]);
}
});
hasAllowableOperations = true;
- component.onNodeSelect({ detail: { node: { entry } } });
- fixture.detectChanges();
+ component.onCurrentSelection([{ entry }]);
hasAllowableOperations = false;
- component.onNodeSelect({ detail: { node: { entry } } });
- fixture.detectChanges();
+ component.onCurrentSelection([{ entry }]);
+
}));
- it('should be null when the chosenNode is reset', async(() => {
+ it('should be empty when the chosenNode is reset', async(() => {
hasAllowableOperations = true;
- component.onNodeSelect({ detail: { node: { entry: {} } } });
- fixture.detectChanges();
+ component.onCurrentSelection([{ entry: {} }]);
component.select.subscribe((nodes) => {
expect(nodes).toBeDefined();
@@ -1043,7 +1037,6 @@ describe('ContentNodeSelectorComponent', () => {
});
component.resetChosenNode();
- fixture.detectChanges();
}));
});
@@ -1060,7 +1053,7 @@ describe('ContentNodeSelectorComponent', () => {
component.select.subscribe((nodes) => {
expect(nodes).toBeDefined();
expect(nodes).not.toBeNull();
- expect(component.chosenNode).not.toBeNull();
+ expect(component.chosenNode[0]).not.toBeNull();
expect(component.isSelectionValid).not.toBeNull();
});
@@ -1070,21 +1063,21 @@ describe('ContentNodeSelectorComponent', () => {
it('should NOT be null after clicking on a node in the list (onNodeSelect)', async(() => {
fixture.detectChanges();
+
component.select.subscribe((nodes) => {
expect(nodes).toBeDefined();
expect(nodes).not.toBeNull();
- expect(component.chosenNode).not.toBeNull();
+ expect(component.chosenNode[0]).not.toBeNull();
+ expect(component.chosenNode[0].id).toBe('fakeid');
expect(component.isSelectionValid).not.toBeNull();
});
- component.onNodeSelect({ detail: { node: { entry } } });
- fixture.detectChanges();
+ component.onCurrentSelection([{ entry }]);
}));
it('should be null when the chosenNode is reset', async(() => {
fixture.detectChanges();
- component.onNodeSelect({ detail: { node: { entry: {} } } });
- fixture.detectChanges();
+ component.onCurrentSelection([{ entry: {} }]);
component.select.subscribe((nodes) => {
expect(nodes).toBeDefined();
@@ -1111,7 +1104,7 @@ describe('ContentNodeSelectorComponent', () => {
component.select.subscribe((nodes) => {
expect(nodes).toBeDefined();
expect(nodes).not.toBeNull();
- expect(component.chosenNode).not.toBeNull();
+ expect(component.chosenNode[0]).not.toBeNull();
expect(component.isSelectionValid).not.toBeNull();
});
@@ -1119,24 +1112,9 @@ describe('ContentNodeSelectorComponent', () => {
fixture.detectChanges();
}));
- it('should NOT be null after clicking on a node in the list (onNodeSelect)', async(() => {
- component.select.subscribe((nodes) => {
- expect(nodes).toBeDefined();
- expect(nodes).not.toBeNull();
- expect(component.chosenNode).not.toBeNull();
- expect(component.isSelectionValid).not.toBeNull();
- });
- fixture.detectChanges();
-
- component.onNodeSelect({ detail: { node: { entry } } });
- fixture.detectChanges();
- }));
-
it('should be null when the chosenNode is reset', async(() => {
fixture.detectChanges();
-
- component.onNodeSelect({ detail: { node: { entry: {} } } });
- fixture.detectChanges();
+ component.onCurrentSelection([{ entry: {} }]);
component.select.subscribe((nodes) => {
expect(nodes).toBeDefined();
@@ -1149,6 +1127,7 @@ describe('ContentNodeSelectorComponent', () => {
fixture.detectChanges();
}));
});
+
});
});
});
diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts
index 9f05b3bfd3..05e36068da 100644
--- a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts
+++ b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts
@@ -26,7 +26,7 @@ import {
SitesService
} from '@alfresco/adf-core';
import { FormControl } from '@angular/forms';
-import { Node, NodePaging, Pagination, SiteEntry, SitePaging } from '@alfresco/js-api';
+import { Node, NodePaging, Pagination, SiteEntry, SitePaging, NodeEntry } from '@alfresco/js-api';
import { DocumentListComponent } from '../document-list/components/document-list.component';
import { RowFilter } from '../document-list/data/row-filter.model';
import { ImageResolver } from '../document-list/data/image-resolver.model';
@@ -132,6 +132,10 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
@Input()
pageSize: number = this.DEFAULT_PAGINATION.maxItems;
+ /** Define the selection mode for document list. The allowed values are single or multiple */
+ @Input()
+ selectionMode: 'single' | 'multiple' = 'single';
+
/** Function used to decide if the selected node has permission to be selected.
* Default value is a function that always returns true.
*/
@@ -198,7 +202,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
showingSearchResults: boolean = false;
loadingSearchResults: boolean = false;
inDialog: boolean = false;
- _chosenNode: Node = null;
+ _chosenNode: Node [] = null;
folderIdToShow: string | null = null;
breadcrumbFolderTitle: string | null = null;
startSiteGuid: string | null = null;
@@ -223,13 +227,9 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
private sitesService: SitesService) {
}
- set chosenNode(value: Node) {
+ set chosenNode(value: Node[]) {
this._chosenNode = value;
- let valuesArray = null;
- if (value) {
- valuesArray = [value];
- }
- this.select.next(valuesArray);
+ this.select.next(value);
}
get chosenNode() {
@@ -334,7 +334,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
let folderNode: Node;
if (this.showingSearchResults && this.chosenNode) {
- folderNode = this.chosenNode;
+ folderNode = this.chosenNode[0];
} else {
folderNode = this.documentList.folderNode;
}
@@ -468,7 +468,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
*/
private attemptNodeSelection(entry: Node): void {
if (entry && this.isSelectionValid(entry)) {
- this.chosenNode = entry;
+ this.chosenNode = [entry];
} else {
this.resetChosenNode();
}
@@ -482,12 +482,14 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
}
/**
- * Invoked when user selects a node
+ * It filters and emit the selection coming from the document list
*
- * @param event CustomEvent for node-select
+ * @param nodesEntries
*/
- onNodeSelect(event: any): void {
- this.attemptNodeSelection(event.detail.node.entry);
+ onCurrentSelection(nodesEntries: NodeEntry[]): void {
+ const validNodesEntity = nodesEntries.filter((node) => this.isSelectionValid(node.entry));
+ const nodes: Node[] = validNodesEntity.map((node) => node.entry );
+ this.chosenNode = nodes;
}
setTitleIfCustomSite(site: SiteEntry) {
diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector.component-data.interface.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector.component-data.interface.ts
index c5c9a488f1..bd15647b3a 100644
--- a/lib/content-services/src/lib/content-node-selector/content-node-selector.component-data.interface.ts
+++ b/lib/content-services/src/lib/content-node-selector/content-node-selector.component-data.interface.ts
@@ -27,6 +27,7 @@ export interface ContentNodeSelectorComponentData {
rowFilter?: any;
where?: string;
imageResolver?: any;
+ selectionMode: string;
isSelectionValid?: (entry: Node) => boolean;
breadcrumbTransform?: (node) => any;
excludeSiteContent?: string[];
diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector.component.html b/lib/content-services/src/lib/content-node-selector/content-node-selector.component.html
index 5362b57cd7..302371d796 100644
--- a/lib/content-services/src/lib/content-node-selector/content-node-selector.component.html
+++ b/lib/content-services/src/lib/content-node-selector/content-node-selector.component.html
@@ -14,6 +14,7 @@
[isSelectionValid]="data?.isSelectionValid"
[breadcrumbTransform]="data?.breadcrumbTransform"
[excludeSiteContent]="data?.excludeSiteContent"
+ [selectionMode]="data?.selectionMode"
[where]="data?.where"
[showSearch]="data?.showSearch"
[showDropdownSiteList]="data?.showDropdownSiteList"
@@ -31,7 +32,7 @@