[ACA-3000][ACA-2999] FE - Provide way to show ContextMenu on Task/Process list (#5596)

* [ACA-3000] FE - Provide way to show ContextMenu on Task list

* * Updated docs

* * Added unit tests to the recent changes
This commit is contained in:
siva kumar
2020-04-14 14:22:57 +05:30
committed by GitHub
parent ecca9220f1
commit 63063699fd
13 changed files with 394 additions and 11 deletions

View File

@@ -270,14 +270,16 @@
},
"TOOLTIP_MESSAGE": {
"START_INPUT": "Starting page"
}
},
"TASK_CONTEXT_MENU": "Task List Context Menu"
},
"PROCESS_LIST_DEMO": {
"ERROR_MESSAGE": {
"APP_ID_REQUIRED_ERROR": "Insert App ID",
"APP_ID_TYPE_ERROR": "App ID must be a number",
"NUMBER_GREATER_THAN": "Value must be greater than or equal to {{ value }}"
}
},
"PROCESS_CONTEXT_MENU": "Process List Context Menu"
},
"GROUP-TITLE1-TRANSLATION-KEY": "CUSTOM TITLE TRANSLATION ONE",
"GROUP-TITLE2-TRANSLATION-KEY": "CUSTOM TITLE TRANSLATION TWO",

View File

@@ -47,6 +47,8 @@
[state]="taskFilter?.filter?.state"
[sort]="taskFilter?.filter?.sort"
[landingTaskId]="currentTaskId"
[showContextMenu]="taskContextMenu"
(showRowContextMenu)="onShowTaskRowContextMenu($event)"
(rowClick)="onTaskRowClick($event)"
(success)="onSuccessTaskList()"
(row-click)="onRowClick($event)"
@@ -161,6 +163,8 @@
[page]="processPage"
[size]="paginationPageSize"
[sort]="processFilter?.filter?.sort"
[showContextMenu]="processContextMenu"
(showRowContextMenu)="onShowProcessRowContextMenu($event)"
(rowClick)="onProcessRowClick($event)"
(row-dblclick)="onProcessRowDblClick($event)"
[multiselect]="multiSelectProcess"
@@ -280,6 +284,12 @@
<mat-slide-toggle id="adf-task-multiselect" [(ngModel)]="multiSelectTask" >Multiselect Task List
</mat-slide-toggle>
</div>
<div>
<mat-slide-toggle id="adf-task-context-menu" [(ngModel)]="taskContextMenu" >Show Task list Context menu</mat-slide-toggle>
</div>
<div>
<mat-slide-toggle id="adf-process-context-menu" [(ngModel)]="processContextMenu" >Show Process list Context menu</mat-slide-toggle>
</div>
<br>
<mat-radio-group [(ngModel)]="selectionMode">
<mat-radio-button value="multiple">multiple</mat-radio-button>

View File

@@ -36,7 +36,7 @@ import {
import {
FORM_FIELD_VALIDATORS, FormRenderingService, FormService,
DynamicTableRow, ValidateDynamicTableRowEvent, AppConfigService, PaginationComponent, UserPreferenceValues,
AlfrescoApiService, UserPreferencesService, LogService
AlfrescoApiService, UserPreferencesService, LogService, DataCellEvent
} from '@alfresco/adf-core';
import { AnalyticsReportListComponent } from '@alfresco/adf-insights';
@@ -118,6 +118,8 @@ export class ProcessServiceComponent implements AfterViewInit, OnDestroy, OnInit
multiSelectTask = false;
multiSelectProcess = false;
selectionMode = 'single';
taskContextMenu = false;
processContextMenu = false;
private tabs = { tasks: 0, processes: 1, reports: 2 };
@@ -526,4 +528,33 @@ export class ProcessServiceComponent implements AfterViewInit, OnDestroy, OnInit
this.currentTaskId = null;
}
onShowTaskRowContextMenu(event: DataCellEvent) {
event.value.actions = [
{
data: event.value.row['obj'],
model: {
key: 'taskDetails',
icon: 'open',
title: 'TASK_LIST_DEMO.TASK_CONTEXT_MENU',
visible: true
},
subject: new Subject()
}
];
}
onShowProcessRowContextMenu(event: DataCellEvent) {
event.value.actions = [
{
data: event.value.row['obj'],
model: {
key: 'processDetails',
icon: 'open',
title: 'PROCESS_LIST_DEMO.PROCESS_CONTEXT_MENU',
visible: true
},
subject: new Subject()
}
];
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -64,6 +64,7 @@ when the process list is empty:
| size | `number` | | The number of processes to fetch in each page. |
| sort | `string` | | Defines the sort ordering of the list. Possible values are `created-desc`, `created-asc`, `ended-desc`, `ended-asc`. |
| state | `string` | | Defines the state of the processes. Possible values are `running`, `completed` and `all` |
| showContextMenu | `boolean` | false | Toggles custom context menu for the component. |
### Events
@@ -72,6 +73,7 @@ when the process list is empty:
| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when an error occurs while loading the list of process instances from the server. |
| rowClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string>` | Emitted when a row in the process list is clicked. |
| success | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`ProcessListModel`](../../../lib/process-services/src/lib/process-list/models/process-list.model.ts)`>` | Emitted when the list of process instances has been loaded successfully from the server. |
| 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
@@ -186,6 +188,48 @@ The Process Instance List also supports pagination:
</adf-pagination>
```
#### showRowContextMenu event
Emitted before the context menu is displayed for a row.
Note that the ProcessInstanceListComponent itself does not populate the context menu with items. You can provide all necessary content via the handler.
```html
<adf-process-instance-list
[contextMenu]="true"
(showRowContextMenu)="onShowRowContextMenu($event)">
</adf-process-instance-list>
```
Event properties:
```ts
value: {
row: DataRow,
col: DataColumn,
actions: []
}
```
Handler example:
```ts
onShowRowContextMenu(event: DataCellEvent) {
event.value.actions = [
{ title: 'Process List Context Menu' },
{ ... }
]
}
```
![](../../docassets/images/process-instance-list-context-menu.png)
This event is cancellable. You can use `event.preventDefault()` to prevent the default behavior.
The ProcessInstanceList 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.
## See also
- [Data column component](../../core/components/data-column.component.md)

View File

@@ -73,6 +73,7 @@ when the task list is empty:
| start | `number` | | Starting point of the list within the full set of tasks. |
| state | `string` | | Current state of the process. Possible values are: `completed`, `active`. |
| taskId | `string` | | The id of a task |
| showContextMenu | `boolean` | false | Toggles custom context menu for the component. |
### Events
@@ -82,6 +83,7 @@ when the task list is empty:
| rowClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string>` | Emitted when a task in the list is clicked |
| rowsSelected | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any[]>` | Emitted when rows are selected/unselected |
| success | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when the task list is loaded |
| 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
@@ -228,6 +230,50 @@ typical tasklist:
You can customize the styling of a column and also add features like tooltips and automatic translation of column titles. See the [Data Column component](../../core/components/data-column.component.md) page for more information about these features.
#### showRowContextMenu event
Emitted before the context menu is displayed for a row.
Note that the TaskListComponent itself does not populate the context menu with items.
You can provide all necessary content via the handler.
```html
<adf-tasklist
[contextMenu]="true"
(showRowContextMenu)="onShowRowContextMenu($event)">
</adf-tasklist>
```
Event properties:
```ts
value: {
row: DataRow,
col: DataColumn,
actions: []
}
```
Handler example:
```ts
onShowRowContextMenu(event: DataCellEvent) {
event.value.actions = [
{ title: 'Task List Context Menu' },
{ ... }
]
}
```
![](../../docassets/images/task-list-context-menu.png)
This event is cancellable. You can use `event.preventDefault()` to prevent the default behavior.
The TaskListComponent 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.
## See also
- [Data column component](../../core/components/data-column.component.md)

View File

@@ -7,6 +7,8 @@
[selectionMode]="selectionMode"
[multiselect]="multiselect"
[resolverFn]="resolverFn"
[contextMenu]="showContextMenu"
(showRowContextMenu)="onShowRowContextMenu($event)"
(rowClick)="onRowClick($event)"
(row-keyup)="onRowKeyUp($event)">
<adf-loading-content-template>

View File

@@ -15,14 +15,14 @@
* limitations under the License.
*/
import { Component, SimpleChange, ViewChild } from '@angular/core';
import { Component, SimpleChange, ViewChild, OnInit, Output, EventEmitter } from '@angular/core';
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
import { of, throwError, Subject } from 'rxjs';
import { By } from '@angular/platform-browser';
import { ProcessInstanceListComponent } from './process-list.component';
import {
AppConfigService, setupTestBed, CoreModule, DataTableModule, DataRow, DataColumn,
DataRowEvent, ObjectDataRow, ObjectDataTableAdapter
DataRowEvent, ObjectDataRow, ObjectDataTableAdapter, DataCellEvent
} from '@alfresco/adf-core';
import { fakeProcessInstance, fakeProcessInstancesWithNoName, fakeProcessInstancesEmpty, fakeProcessCustomSchema } from '../../mock';
import { ProcessService } from '../services/process.service';
@@ -527,3 +527,115 @@ describe('Process List: Custom EmptyTemplateComponent', () => {
});
});
});
@Component({
template: `
<adf-process-instance-list
[appId]="appId"
[showContextMenu]="true"
(showRowContextMenu)="onShowRowContextMenu($event)"
#processlistComponentInstance>
<data-columns>
<data-column key="name" title="ADF_PROCESS_LIST.PROPERTIES.NAME" class="adf-full-width adf-name-column"></data-column>
<data-column key="created" title="ADF_PROCESS_LIST.PROPERTIES.END_DATE" class="adf-hidden"></data-column>
<data-column key="startedBy" title="ADF_PROCESS_LIST.PROPERTIES.CREATED" class="adf-desktop-only dw-dt-col-3 adf-ellipsis-cell">
<ng-template let-entry="$implicit">
<div>{{entry.row.obj.startedBy | fullName}}</div>
</ng-template>
</data-column>
</data-columns>
</adf-process-instance-list>`
})
class ProcessListContextMenuComponent implements OnInit {
appId: number;
@Output()
contextAction = new EventEmitter<any>();
private performAction$ = new Subject<any>();
ngOnInit() {
this.performContextActions();
}
onShowRowContextMenu(event: DataCellEvent) {
event.value.actions = [
{
data: event.value.row['obj'],
model:
{
key: 'processDetails',
icon: 'open',
title: 'View Process Details',
visible: true
},
subject: this.performAction$
},
{
data: event.value.row['obj'],
model:
{
key: 'cancel',
icon: 'open',
title: 'Cancel Process',
visible: true
},
subject: this.performAction$
}
];
}
performContextActions() {
this.performAction$
.subscribe((action: any) => {
this.contextAction.emit(action.data);
});
}
}
describe('ProcessListContextMenuComponent', () => {
let fixture: ComponentFixture<ProcessListContextMenuComponent>;
let customComponent: ProcessListContextMenuComponent;
let processService: ProcessService;
let element: HTMLElement;
setupTestBed({
imports: [CoreModule.forRoot()],
declarations: [ProcessInstanceListComponent, ProcessListContextMenuComponent],
providers: [ProcessService]
});
beforeEach(() => {
fixture = TestBed.createComponent(ProcessListContextMenuComponent);
customComponent = fixture.componentInstance;
element = fixture.nativeElement;
processService = TestBed.get(ProcessService);
customComponent.appId = 12345;
spyOn(processService, 'getProcesses').and.returnValue(of(fakeProcessInstance));
fixture.detectChanges();
});
afterEach(() => {
const event = new KeyboardEvent('keydown', {
bubbles : true, cancelable : true, key : 'Escape'
});
document.querySelector('.cdk-overlay-backdrop').dispatchEvent(event);
fixture.detectChanges();
});
it('Should be able to show context menu on process list', async () => {
const contextMenu = element.querySelector(`[data-automation-id="text_${fakeProcessInstance.data[0].name}"]`);
const contextActionSpy = spyOn(customComponent.contextAction, 'emit').and.callThrough();
contextMenu.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true }));
fixture.detectChanges();
await fixture.whenStable();
const contextActions = document.querySelectorAll('.mat-menu-item');
expect(contextActions.length).toBe(2);
expect(contextActions[0]['disabled']).toBe(false, 'View Process Details action not enabled');
expect(contextActions[1]['disabled']).toBe(false, 'Cancel Process action not enabled');
contextActions[0].dispatchEvent(new Event('click'));
fixture.detectChanges();
expect(contextActionSpy).toHaveBeenCalled();
});
});

View File

@@ -27,7 +27,8 @@ import {
PaginatedComponent,
PaginationComponent,
PaginationModel,
UserPreferencesService
UserPreferencesService,
DataCellEvent
} from '@alfresco/adf-core';
import {
AfterContentInit,
@@ -110,6 +111,14 @@ export class ProcessInstanceListComponent extends DataTableSchema implements OnC
@Input()
selectFirstRow: boolean = true;
/** Toggles custom context menu for the component. */
@Input()
showContextMenu: boolean = false;
/** Emitted before the context menu is displayed for a row. */
@Output()
showRowContextMenu = new EventEmitter<DataCellEvent>();
/**
* Resolver function is used to show dynamic complex column objects
* see the docs to learn how to configure a resolverFn.
@@ -284,6 +293,10 @@ export class ProcessInstanceListComponent extends DataTableSchema implements OnC
}
}
onShowRowContextMenu(event: DataCellEvent) {
this.showRowContextMenu.emit(event);
}
private createRequestNode(): ProcessFilterParamRepresentationModel {
return new ProcessFilterParamRepresentationModel({
appDefinitionId: this.appId,

View File

@@ -8,6 +8,8 @@
[loading]="isLoading"
[multiselect]="multiselect"
[selectionMode]="selectionMode"
[contextMenu]="showContextMenu"
(showRowContextMenu)="onShowRowContextMenu($event)"
(row-select)="onRowSelect($event)"
(row-unselect)="onRowUnselect($event)"
(rowClick)="onRowClick($event)"

View File

@@ -15,16 +15,16 @@
* limitations under the License.
*/
import { Component, SimpleChange, ViewChild } from '@angular/core';
import { Component, SimpleChange, ViewChild, OnInit, Output, EventEmitter } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppConfigService, setupTestBed, CoreModule, DataTableModule, DataRowEvent, ObjectDataRow } from '@alfresco/adf-core';
import { AppConfigService, setupTestBed, CoreModule, DataTableModule, DataRowEvent, ObjectDataRow, DataCellEvent } from '@alfresco/adf-core';
import { TaskListService } from '../services/tasklist.service';
import { TaskListComponent } from './task-list.component';
import { ProcessTestingModule } from '../../testing/process.testing.module';
import { fakeGlobalTask, fakeCustomSchema, fakeEmptyTask, paginatedTask } from '../../mock';
import { TranslateService } from '@ngx-translate/core';
import { of } from 'rxjs';
import { of, Subject } from 'rxjs';
import { TaskListModule } from '../task-list.module';
declare let jasmine: any;
@@ -807,3 +807,112 @@ describe('Task List: Custom EmptyTemplateComponent', () => {
});
});
});
@Component({
template: `
<adf-tasklist
[showContextMenu]="true"
(showRowContextMenu)="onShowRowContextMenu($event)"
#taskList>
<data-columns>
<data-column key="name" title="ADF_TASK_LIST.PROPERTIES.NAME" class="full-width name-column"></data-column>
<data-column key="created" title="ADF_TASK_LIST.PROPERTIES.CREATED" class="hidden"></data-column>
<data-column key="startedBy" title="ADF_TASK_LIST.PROPERTIES.CREATED" class="desktop-only dw-dt-col-3 ellipsis-cell">
<ng-template let-entry="$implicit">
<div>{{entry.row?.obj?.startedBy | fullName}}</div>
</ng-template>
</data-column>
</data-columns>
</adf-tasklist>`
})
class TaskListContextMenuComponent implements OnInit {
@Output()
contextAction = new EventEmitter<any>();
private performAction$ = new Subject<any>();
ngOnInit() {
this.performContextActions();
}
onShowRowContextMenu(event: DataCellEvent) {
event.value.actions = [
{
data: event.value.row['obj'],
model:
{
key: 'taskDetails',
icon: 'open',
title: 'View Task Details',
visible: true
},
subject: this.performAction$
},
{
data: event.value.row['obj'],
model:
{
key: 'cancel',
icon: 'open',
title: 'Cancel Process',
visible: true
},
subject: this.performAction$
}
];
}
performContextActions() {
this.performAction$
.subscribe((action: any) => {
this.contextAction.emit(action.data);
});
}
}
describe('TaskListContextMenuComponent', () => {
let fixture: ComponentFixture<TaskListContextMenuComponent>;
let customComponent: TaskListContextMenuComponent;
let taskListService: TaskListService;
let element: HTMLElement;
setupTestBed({
imports: [CoreModule.forRoot()],
declarations: [TaskListComponent, TaskListContextMenuComponent],
providers: [TaskListService]
});
beforeEach(() => {
fixture = TestBed.createComponent(TaskListContextMenuComponent);
customComponent = fixture.componentInstance;
element = fixture.nativeElement;
taskListService = TestBed.get(TaskListService);
spyOn(taskListService, 'findTasksByState').and.returnValues(of(fakeGlobalTask));
fixture.detectChanges();
});
afterEach(() => {
const event = new KeyboardEvent('keydown', {
bubbles : true, cancelable : true, key : 'Escape'
});
document.querySelector('.cdk-overlay-backdrop').dispatchEvent(event);
fixture.detectChanges();
});
it('Should be able to show context menu on task list', async () => {
const contextMenu = element.querySelector(`[data-automation-id="text_${fakeGlobalTask.data[0].name}"]`);
const contextActionSpy = spyOn(customComponent.contextAction, 'emit').and.callThrough();
contextMenu.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true }));
fixture.detectChanges();
await fixture.whenStable();
const contextActions = document.querySelectorAll('.mat-menu-item');
expect(contextActions.length).toBe(2);
expect(contextActions[0]['disabled']).toBe(false, 'View Task Details action not enabled');
expect(contextActions[1]['disabled']).toBe(false, 'Cancel Task action not enabled');
contextActions[0].dispatchEvent(new Event('click'));
fixture.detectChanges();
expect(contextActionSpy).toHaveBeenCalled();
});
});

View File

@@ -18,7 +18,7 @@
import {
DataRowEvent, DataTableAdapter, DataTableSchema, CustomEmptyContentTemplateDirective, CustomLoadingContentTemplateDirective,
AppConfigService, PaginationComponent, PaginatedComponent,
UserPreferencesService, UserPreferenceValues, PaginationModel } from '@alfresco/adf-core';
UserPreferencesService, UserPreferenceValues, PaginationModel, DataCellEvent } from '@alfresco/adf-core';
import {
AfterContentInit, Component, ContentChild, EventEmitter,
Input, OnChanges, Output, SimpleChanges, OnDestroy, OnInit } from '@angular/core';
@@ -123,6 +123,14 @@ export class TaskListComponent extends DataTableSchema implements OnChanges, Aft
@Input()
start: number;
/** Toggles custom context menu for the component. */
@Input()
showContextMenu: boolean = false;
/** Emitted before the context menu is displayed for a row. */
@Output()
showRowContextMenu = new EventEmitter<DataCellEvent>();
/** Emitted when a task in the list is clicked */
@Output()
rowClick = new EventEmitter<string>();
@@ -356,6 +364,10 @@ export class TaskListComponent extends DataTableSchema implements OnChanges, Aft
}
}
onShowRowContextMenu(event: DataCellEvent) {
this.showRowContextMenu.emit(event);
}
/**
* Optimize name field
* @param instances