diff --git a/demo-shell/resources/i18n/en.json b/demo-shell/resources/i18n/en.json index 9fb603b5f9..6f13ce31f6 100644 --- a/demo-shell/resources/i18n/en.json +++ b/demo-shell/resources/i18n/en.json @@ -320,7 +320,21 @@ "MULTISELECTION": "Multiselection", "TESTING_MODE": "Testing Mode", "SELECTION_MODE": "Selection Mode", + "SELECTED_ROWS": "Selected rows", "TASK_DETAILS_REDIRECTION": "Display task details on task click", - "PROCESS_DETAILS_REDIRECTION": "Display process details on process click" + "PROCESS_DETAILS_REDIRECTION": "Display process details on process click", + "ACTION": { + "ACTION_TITLE": "Add Action", + "ACTION_MENU": "Action Menu", + "CONTEX_MENU": "Context Menu", + "ACTION_VISIBLE": "Visible", + "ACTION_DISABLE": "Disable", + "ADD_BUTTON": "Add", + "KEY": "Key", + "TITLE": "Title", + "ICON": "Icon", + "PROCESS_ID": "ProcessInstanceId", + "ACTION_TYPE": "Action Type" + } } } diff --git a/demo-shell/src/app/components/cloud/processes-cloud-demo.component.html b/demo-shell/src/app/components/cloud/processes-cloud-demo.component.html index af3a0ec943..70dfb611ac 100644 --- a/demo-shell/src/app/components/cloud/processes-cloud-demo.component.html +++ b/demo-shell/src/app/components/cloud/processes-cloud-demo.component.html @@ -24,7 +24,13 @@ [lastModifiedTo]="editedFilter.lastModifiedTo" [sorting]="sortArray" [selectionMode]="selectionMode" + [stickyHeader]="true" + [showActions]="actionMenu" + [showContextMenu]="contextMenu" [multiselect]="multiselect" + (showRowActionsMenu)="onShowRowActionsMenu($event)" + (showRowContextMenu)="onShowRowContextMenu($event)" + (executeRowAction)="onExecuteRowAction($event)" (rowClick)="onRowClick($event)" (rowsSelected)="onRowsSelected($event)"> @@ -35,10 +41,28 @@ (prevPage)="resetSelectedRows()">
- Selected rows: - +
+ {{ 'SETTINGS_CLOUD.SELECTED_ROWS' | translate }}: + +
+
+ {{ 'SETTINGS_CLOUD.ACTION.ACTION_MENU' | translate }}: +
+
+ {{ 'SETTINGS_CLOUD.ACTION.PROCESS_ID' | translate }}: {{ selectedAction.id }}
+ {{ 'SETTINGS_CLOUD.ACTION.ACTION_TYPE' | translate }}: {{ selectedAction.actionType }} +
+
+
+ {{ 'SETTINGS_CLOUD.ACTION.CONTEX_MENU' | translate }}: +
+
+ {{ 'SETTINGS_CLOUD.ACTION.PROCESS_ID' | translate }}: {{ selectedContextAction.id }}
+ {{ 'SETTINGS_CLOUD.ACTION.ACTION_TYPE' | translate }}: {{ selectedContextAction.actionType }} +
+
diff --git a/demo-shell/src/app/components/cloud/processes-cloud-demo.component.ts b/demo-shell/src/app/components/cloud/processes-cloud-demo.component.ts index 40455ff8c2..87f77c13bc 100644 --- a/demo-shell/src/app/components/cloud/processes-cloud-demo.component.ts +++ b/demo-shell/src/app/components/cloud/processes-cloud-demo.component.ts @@ -24,7 +24,7 @@ import { } from '@alfresco/adf-process-services-cloud'; import { ActivatedRoute, Router } from '@angular/router'; -import { UserPreferencesService, AppConfigService } from '@alfresco/adf-core'; +import { UserPreferencesService, AppConfigService, DataCellEvent } from '@alfresco/adf-core'; import { CloudLayoutService, CloudServiceSettings } from './services/cloud-layout.service'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -55,11 +55,17 @@ export class ProcessesCloudDemoComponent implements OnInit, OnDestroy { selectionMode: string; selectedRows: string[] = []; testingMode: boolean; + actionMenu: boolean; + contextMenu: boolean; + actions: any[] = []; + selectedAction: { id: number, name: string, actionType: string}; + selectedContextAction: { id: number, name: string, actionType: string}; processFilterProperties: any = { filterProperties: [], sortProperties: [], actions: [] }; processDetailsRedirection: boolean; editedFilter: ProcessFilterCloudModel; + private performAction$ = new Subject(); private onDestroy$ = new Subject(); constructor( @@ -89,6 +95,7 @@ export class ProcessesCloudDemoComponent implements OnInit, OnDestroy { this.cloudLayoutService.settings$ .pipe(takeUntil(this.onDestroy$)) .subscribe(settings => this.setCurrentSettings(settings)); + this.performContextActions(); } ngOnDestroy() { @@ -102,6 +109,9 @@ export class ProcessesCloudDemoComponent implements OnInit, OnDestroy { this.testingMode = settings.testingMode; this.selectionMode = settings.selectionMode; this.processDetailsRedirection = settings.processDetailsRedirection; + this.actionMenu = settings.actionMenu; + this.contextMenu = settings.contextMenu; + this.actions = settings.actions; } } @@ -140,4 +150,41 @@ export class ProcessesCloudDemoComponent implements OnInit, OnDestroy { this.resetSelectedRows(); this.selectedRows = nodes.map((node) => node.obj.entry); } + + onShowRowActionsMenu(event: DataCellEvent) { + event.value.actions = this.actions; + } + + onShowRowContextMenu(event: DataCellEvent) { + event.value.actions = this.actions.map((action) => { + return { + data: event.value.row['obj'], + model: action, + subject: this.performAction$ + + }; + }); + } + + onExecuteRowAction(row: any) { + const value = row.value.row['obj'].entry; + const action = row.value.action; + this.selectedAction = {id: value.id, name: value.name, actionType: action.title}; + } + + performContextActions() { + this.performAction$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe((action: any) => { + if (action) { + this.onExecuteContextAction(action); + } + }); + } + + onExecuteContextAction(contextAction: any) { + const value = contextAction.data.entry; + const action = contextAction.model; + this.selectedContextAction = {id: value.id, name: value.name, actionType: action.title}; + } } diff --git a/demo-shell/src/app/components/cloud/services/cloud-layout.service.ts b/demo-shell/src/app/components/cloud/services/cloud-layout.service.ts index d3041063d3..4e86e2aa5a 100644 --- a/demo-shell/src/app/components/cloud/services/cloud-layout.service.ts +++ b/demo-shell/src/app/components/cloud/services/cloud-layout.service.ts @@ -20,10 +20,13 @@ import { BehaviorSubject } from 'rxjs'; export interface CloudServiceSettings { multiselect: boolean; + actionMenu: boolean; + contextMenu: boolean; testingMode: boolean; taskDetailsRedirection: boolean; processDetailsRedirection: boolean; selectionMode: string; + actions: any[]; } export interface FilterSettings { @@ -32,6 +35,17 @@ export interface FilterSettings { key?: string; } +export class ActionMenuModel { + constructor( + public key: string, + public icon: string, + public title: string, + public visible?: boolean, + public disable?: boolean + ) { } + +} + @Injectable({ providedIn: 'root' }) @@ -39,10 +53,13 @@ export class CloudLayoutService { private settings: CloudServiceSettings = { multiselect: false, + actionMenu: false, + contextMenu: false, testingMode: false, taskDetailsRedirection: true, processDetailsRedirection: true, - selectionMode: 'single' + selectionMode: 'single', + actions: [] }; taskFilter$ = new BehaviorSubject({index: 0}); diff --git a/demo-shell/src/app/components/cloud/shared/cloud-settings.component.html b/demo-shell/src/app/components/cloud/shared/cloud-settings.component.html index 62d8ded230..5c385dcbdd 100644 --- a/demo-shell/src/app/components/cloud/shared/cloud-settings.component.html +++ b/demo-shell/src/app/components/cloud/shared/cloud-settings.component.html @@ -2,6 +2,12 @@ {{ 'SETTINGS_CLOUD.MULTISELECTION' | translate }} + + {{ 'SETTINGS_CLOUD.ACTION.ACTION_MENU' | translate }} + + + {{ 'SETTINGS_CLOUD.ACTION.CONTEX_MENU' | translate }} + {{ 'SETTINGS_CLOUD.TESTING_MODE' | translate }} @@ -21,4 +27,45 @@ + + + {{ 'SETTINGS_CLOUD.ACTION.ACTION_TITLE' | translate }} + + +
+ + + + + + + + + + + + + {{ 'SETTINGS_CLOUD.ACTION.ACTION_VISIBLE' | translate }} + + + {{ 'SETTINGS_CLOUD.ACTION.ACTION_DISABLE' | translate }} + + +
+
+ + + {{action.title}} + + cancel + + + +
+
+
diff --git a/demo-shell/src/app/components/cloud/shared/cloud-settings.component.ts b/demo-shell/src/app/components/cloud/shared/cloud-settings.component.ts index d21e308386..b9ca5fec4e 100644 --- a/demo-shell/src/app/components/cloud/shared/cloud-settings.component.ts +++ b/demo-shell/src/app/components/cloud/shared/cloud-settings.component.ts @@ -16,9 +16,10 @@ */ import { Component, OnInit, OnDestroy } from '@angular/core'; -import { CloudLayoutService } from '../services/cloud-layout.service'; +import { CloudLayoutService, ActionMenuModel } from '../services/cloud-layout.service'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { FormGroup, FormControl } from '@angular/forms'; @Component({ selector: 'app-cloud-settings', @@ -29,6 +30,9 @@ export class CloudSettingsComponent implements OnInit, OnDestroy { private onDestroy$ = new Subject(); multiselect: boolean; + actionMenu: boolean; + contextMenu: boolean; + actions: ActionMenuModel[] = []; selectionMode: string; testingMode: boolean; taskDetailsRedirection: boolean; @@ -40,6 +44,14 @@ export class CloudSettingsComponent implements OnInit, OnDestroy { { value: 'multiple', title: 'Multiple' } ]; + actionMenuForm = new FormGroup({ + key: new FormControl(''), + title: new FormControl(''), + icon: new FormControl(''), + visible: new FormControl(true), + disable: new FormControl(false) + }); + constructor(private cloudLayoutService: CloudLayoutService) { } ngOnInit() { @@ -57,6 +69,9 @@ export class CloudSettingsComponent implements OnInit, OnDestroy { setCurrentSettings(settings) { if (settings) { this.multiselect = settings.multiselect; + this.actionMenu = this.actionMenu; + this.contextMenu = this.contextMenu; + this.actions = this.actions; this.testingMode = settings.testingMode; this.selectionMode = settings.selectionMode; this.taskDetailsRedirection = settings.taskDetailsRedirection; @@ -88,9 +103,35 @@ export class CloudSettingsComponent implements OnInit, OnDestroy { this.setSetting(); } + toggleActionMenu() { + this.actionMenu = !this.actionMenu; + this.setSetting(); + } + + toggleContextMenu() { + this.contextMenu = !this.contextMenu; + this.setSetting(); + } + + addAction() { + this.actions.push( this.actionMenuForm.value); + this.actionMenuForm.get('key').reset(); + this.actionMenuForm.get('title').reset(); + this.actionMenuForm.get('icon').reset(); + this.setSetting(); + } + + removeAction(removedAction: ActionMenuModel) { + this.actions = this.actions.filter((action: ActionMenuModel) => action.key !== removedAction.key); + this.setSetting(); + } + setSetting() { this.cloudLayoutService.setCurrentSettings({ multiselect: this.multiselect, + actionMenu: this.actionMenu, + contextMenu: this.contextMenu, + actions: this.actions, testingMode: this.testingMode, selectionMode: this.selectionMode, taskDetailsRedirection: this.taskDetailsRedirection, diff --git a/demo-shell/src/app/components/cloud/shared/cloud.shared.module.ts b/demo-shell/src/app/components/cloud/shared/cloud.shared.module.ts index a67fc6e948..7a72006f9d 100644 --- a/demo-shell/src/app/components/cloud/shared/cloud.shared.module.ts +++ b/demo-shell/src/app/components/cloud/shared/cloud.shared.module.ts @@ -20,6 +20,7 @@ import { CommonModule } from '@angular/common'; import { CloudSettingsComponent } from './cloud-settings.component'; import { MatDialogModule, MatInputModule, MatSelectModule, MatSlideToggleModule } from '@angular/material'; import { CoreModule } from '@alfresco/adf-core'; +import { FlexLayoutModule } from '@angular/flex-layout'; @NgModule({ imports: [ @@ -28,7 +29,8 @@ import { CoreModule } from '@alfresco/adf-core'; MatDialogModule, MatInputModule, MatSelectModule, - MatSlideToggleModule + MatSlideToggleModule, + FlexLayoutModule ], declarations: [ CloudSettingsComponent ], exports: [ CommonModule, CloudSettingsComponent] diff --git a/docs/docassets/images/action-menu-on-process-list.png b/docs/docassets/images/action-menu-on-process-list.png new file mode 100644 index 0000000000..bf16ea6b6a Binary files /dev/null and b/docs/docassets/images/action-menu-on-process-list.png differ diff --git a/docs/docassets/images/context-menu-on-process-list.png b/docs/docassets/images/context-menu-on-process-list.png new file mode 100644 index 0000000000..2ea60e48a3 Binary files /dev/null and b/docs/docassets/images/context-menu-on-process-list.png differ diff --git a/docs/process-services-cloud/components/process-list-cloud.component.md b/docs/process-services-cloud/components/process-list-cloud.component.md index 5b12220bed..1f7ad0b23c 100644 --- a/docs/process-services-cloud/components/process-list-cloud.component.md +++ b/docs/process-services-cloud/components/process-list-cloud.component.md @@ -51,6 +51,9 @@ when the process list is empty: | Name | Type | Default value | Description | | ---- | ---- | ------------- | ----------- | | appName | `string` | | The name of the application. | +| actions | `boolean` | false | Toggles the data actions column. | +| actionsPosition | `string` | "right" | Position of the actions dropdown menu. Can be "left" or "right". | +| contextMenu | `boolean` | false | Toggles custom context menu for the component. | | businessKey | `string` | "" | Filter the processes to display only the ones with this businessKey value. | | id | `string` | "" | Filter the processes to display only the ones with this ID. | | initiator | `string` | "" | Name of the initiator of the process. | @@ -73,6 +76,9 @@ when the process list is empty: | rowClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when a row in the process list is clicked. | | rowsSelected | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when rows are selected/unselected. | | success | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when the list of process instances has been loaded successfully from the server. | +| executeRowAction | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataRowActionEvent`](../../../lib/core/datatable/components/datatable/data-row-action.event.ts)`>` | Emitted when the user executes a row action. | +| showRowActionsMenu | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataCellEvent`](../../../lib/core/datatable/components/datatable/data-cell.event.ts)`>` | Emitted before the actions menu is displayed for a row. | +| showRowContextMenu | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataCellEvent`](../../../lib/core/datatable/components/datatable/data-cell.event.ts)`>` | Emitted before the context menu is displayed for a row. | ## Details @@ -192,6 +198,116 @@ The configuration related to the pagination can be changed from the `app.config. }, ``` +#### showRowContextMenu event + +Emitted before the context menu is displayed for a row. + +Note that the ProcessListCloud itself does not populate the context menu with items. +You can provide all necessary content via the handler. + +```html + + +``` + +Event properties: + +```ts +value: { + row: DataRow, + col: DataColumn, + actions: [] +} +``` + +Handler example: + +```ts +onShowRowContextMenu(event: DataCellEvent) { + event.value.actions = [ + { title: 'Hello Context Action' }, + { ... } + ] +} +``` +![](../../docassets/images/context-menu-on-process-list.png) + +This event is cancellable. You can use `event.preventDefault()` to prevent the default behavior. + +The ProcessListCloud will automatically render the supplied menu items. + +See the [ContextMenu](https://www.npmjs.com/package/ng2-alfresco-core) +documentation for more details on the format and behavior of context actions. + +#### showRowActionsMenu event + +Emitted before the actions menu is displayed for a row. +Requires the `actions` property to be set to `true`. + +Event properties: + +```ts +value: { + row: DataRow, + action: any +} +``` + +Note that the ProcessListCloud itself does not populate the action menu with items. +You can provide all necessary content via the handler. + +This event is cancellable. You can use `event.preventDefault()` to prevent the default behavior. + +#### executeRowAction event + +Emitted when the user executes a row action. + +This usually accompanies a `showRowActionsMenu` event. +The ProcessListCloud itself does not execute actions but provides support for external +integration. If actions are provided using the `showRowActionsMenu` event +then `executeRowAction` will be automatically executed when the user clicks a +corresponding menu item. + +```html + + +``` + +```ts +import { DataCellEvent, DataRowActionEvent } from '@alfresco/adf-core'; + +onShowRowActionsMenu(event: DataCellEvent) { + let myAction = { + title: 'Hello Action' + // your custom metadata needed for onExecuteRowAction + }; + event.value.actions = [ + myAction + ]; +} + +onExecuteRowAction(event: DataRowActionEvent) { + let args = event.value; + console.log(args.row); + console.log(args.action); + window.alert(`My custom action: ${args.action.title}`); +} +``` + +![](../../docassets/images/action-menu-on-process-list.png) + +You can use any payloads for row actions. The only requirement for the objects is that they +must have a `title` property. + +When an action is selected in the dropdown menu, the ProcessListCloud invokes the `executeRowAction` event. +Use this to handle the response, inspect the action payload (and all custom properties defined +earlier), and perform the corresponding actions. + ## See also - [Data column component](../../core/components/data-column.component.md) diff --git a/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.html b/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.html index ab135609ab..70a8338452 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.html +++ b/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.html @@ -1,9 +1,16 @@ = new EventEmitter(); @@ -108,6 +125,18 @@ export class ProcessListCloudComponent extends DataTableSchema implements OnChan @Output() rowsSelected: EventEmitter = new EventEmitter(); + /** Emitted before the context menu is displayed for a row. */ + @Output() + showRowContextMenu = new EventEmitter(); + + /** Emitted before the actions menu is displayed for a row. */ + @Output() + showRowActionsMenu = new EventEmitter(); + + /** Emitted when the user executes a row action. */ + @Output() + executeRowAction = new EventEmitter(); + /** Emitted when an error occurs while loading the list of process instances from the server. */ @Output() error: EventEmitter = new EventEmitter(); @@ -223,6 +252,18 @@ export class ProcessListCloudComponent extends DataTableSchema implements OnChan } } + onShowRowActionsMenu(event: DataCellEvent) { + this.showRowActionsMenu.emit(event); + } + + onShowRowContextMenu(event: DataCellEvent) { + this.showRowContextMenu.emit(event); + } + + onExecuteRowAction(row: DataRowActionEvent) { + this.executeRowAction.emit(row); + } + private createRequestNode(): ProcessQueryCloudRequestModel { const requestNode = { appName: this.appName,