AAE-20480 Full Screen User Task Forms (#9341)

* AAE-20480 Full Screen User Task Forms

* AAE-20480 Attend review comments

* AAE-20480 Remove leftovers

* AAE-20480 Enable changing the display mode from task-form-cloud

* AAE-20480 Fix fullscreen mode header

* AAE-20480 Fix review comments

* AAE-20480 Allow hiding the full screen toolbar by configuration

* AAE-20480 Add review comments

* AAE-20480 Create display mode service
This commit is contained in:
Pablo Martinez
2024-03-05 15:10:50 +01:00
committed by GitHub
parent 530899fd51
commit dc88ef8ce7
15 changed files with 969 additions and 55 deletions

View File

@@ -95,6 +95,7 @@ The template defined inside `empty-form` will be shown when no form definition i
| showTitle | `boolean` | true | Toggle rendering of the form title. |
| showValidationIcon | `boolean` | true | Toggle rendering of the validation icon next to the form title. |
| taskId | `string` | | Task id to fetch corresponding form and values. |
| displayModeConfigurations | [`FormCloudDisplayModeConfiguration`](../../../lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts)`[]` | | The available display configurations for the form |
### Events
@@ -108,6 +109,8 @@ The template defined inside `empty-form` will be shown when no form definition i
| formError | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormFieldModel`](../../core/models/form-field.model.md)`[]>` | Emitted when the supplied form values have a validation error. |
| formLoaded | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/src/lib/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is loaded or reloaded. |
| formSaved | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/src/lib/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is submitted with the `Save` or custom outcomes. |
| displayModeOn | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormCloudDisplayModeConfiguration`](../../../lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts)`>` | Emitted when a display mode configuration is turned on. |
| displayModeOff | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormCloudDisplayModeConfiguration`](../../../lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts)`>` | Emitted when a display mode configuration is turned off. |
## Details
@@ -160,6 +163,41 @@ For an existing Task both the form and its values will be fetched and displayed.
In this case, only the form definition will be fetched.
#### Enabling fullscreen display for the form
Provide a `displayModeConfiguration` array object containing the fullscreen configuration. You can use the configuration provided in the [`DisplayModeService`](../../../lib/process-services-cloud/src/lib/form/services/display-mode.service.ts) as a static member `DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS`, or configure your own if you want to customise the options for the fullscreen display mode.
**MyView.component.html**
```html
<button (click)="adfCloudForm.switchToDisplayMode('fullScreen')">Full screen</button>
<adf-cloud-form #adfCloudForm
[appName]="appName"
[taskId]="selectedTask?.id"
[showTitle]="false"
[showRefreshButton]="false"
[showValidationIcon]="false"
[displayModeConfigurations]="displayConfigurations">
</adf-cloud-form>
```
**MyView.component.ts**
```ts
import { DisplayModeService } from '@alfresco/adf-process-services-cloud';
export class MyView {
get displayConfigurations() {
return DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS;
}
}
```
When the `displayModeConfigurations` contains the configuration for the fullscreen display, in the header of the form, a button to switch to fullscreen is displayed. Keep in mind that the header of the form is visible only if any of the parameters `showTitle`, `showRefreshButton`or `showValidationIcon` is `true`, but it is also possible to switch to the fullscreen display using a button that you can place wherever you want as shown in the previous example.
### Controlling outcome execution behaviour
In unusual circumstances, you may need to take complete control of form outcome execution.

View File

@@ -43,6 +43,7 @@ Save and Complete buttons get disabled when at least one of the form's inputs ar
| showTitle | `boolean` | true | Toggle rendering of the form title. |
| showValidationIcon | `boolean` | true | Toggle rendering of the `Validation` icon. |
| taskId | `string` | | Task id to fetch corresponding form and values. |
| displayModeConfigurations | [`FormCloudDisplayModeConfiguration`](../../../lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts)`[]` | | The available display configurations for the form |
### Events
@@ -58,6 +59,43 @@ Save and Complete buttons get disabled when at least one of the form's inputs ar
| taskClaimed | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string>` | Emitted when the task is claimed. |
| taskCompleted | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string>` | Emitted when the task is completed. |
| taskUnclaimed | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string>` | Emitted when the task is unclaimed. |
| displayModeOn | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormCloudDisplayModeConfiguration`](../../../lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts)`>` | Emitted when a display mode configuration is turned on. |
| displayModeOff | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormCloudDisplayModeConfiguration`](../../../lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts)`>` | Emitted when a display mode configuration is turned off. |
#### Enabling fullscreen display for the form of the task
Provide a `displayModeConfiguration` array object containing the fullscreen configuration. You can use the configuration provided in the [`DisplayModeService`](../../../lib/process-services-cloud/src/lib/form/services/display-mode.service.ts) as a static member `DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS`, or configure your own if you want to customise the options for the fullscreen display mode.
**MyView.component.html**
```html
<button (click)="adfCloudTaskForm.switchToDisplayMode('fullScreen')">Full screen</button>
<adf-cloud-task-form #adfCloudTaskForm
[appName]="appName"
[taskId]="selectedTask?.id"
[showTitle]="false"
[showRefreshButton]="false"
[showValidationIcon]="false"
[displayModeConfigurations]="displayConfigurations">
</adf-cloud-task-form>
```
**MyView.component.ts**
```ts
import { DisplayModeService } from '@alfresco/adf-process-services-cloud';
export class MyView {
get displayConfigurations() {
return DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS;
}
}
```
When the `displayModeConfigurations` contains the configuration for the fullscreen display, in the header of the form, a button to switch to fullscreen is displayed. Keep in mind that the header of the form is visible only if any of the parameters `showTitle`, `showRefreshButton`or `showValidationIcon` is `true`, but it is also possible to switch to the fullscreen display using a button that you can place wherever you want as shown in the previous example.
## See also

View File

@@ -57,6 +57,7 @@ export interface FormRepresentationModel {
[key: string]: any;
fields?: any[];
};
displayMode: string;
}
export class FormModel implements ProcessFormModel {
static UNSET_TASK_NAME: string = 'Nameless task';
@@ -72,6 +73,7 @@ export class FormModel implements ProcessFormModel {
readonly processDefinitionId: string;
readonly selectedOutcome: string;
readonly enableFixedSpace: boolean;
readonly displayMode: any;
fieldsCache: FormFieldModel[] = [];
@@ -113,6 +115,7 @@ export class FormModel implements ProcessFormModel {
this.processVariables = json.processVariables || [];
this.enableFixedSpace = enableFixedSpace;
this.confirmMessage = json.confirmMessage || {};
this.displayMode = json.displayMode;
this.tabs = (json.tabs || []).map((tabJson) => new TabModel(this, tabJson));

View File

@@ -3,47 +3,81 @@
</ng-content>
</div>
<div *ngIf="hasForm()" class="adf-form-container">
<mat-card class="adf-form-container-card">
<mat-card-header *ngIf="showTitle || showRefreshButton || showValidationIcon">
<mat-card-title>
<h4>
<div *ngIf="showValidationIcon" class="adf-form-validation-button">
<i id="adf-valid-form-icon" class="material-icons"
*ngIf="form.isValid; else no_valid_form">check_circle</i>
<ng-template #no_valid_form>
<i id="adf-invalid-form-icon" class="material-icons adf-invalid-color">error</i>
</ng-template>
</div>
<div *ngIf="showRefreshButton" class="adf-form-reload-button">
<button mat-icon-button (click)="onRefreshClicked()">
<mat-icon>refresh</mat-icon>
</button>
</div>
<span *ngIf="isTitleEnabled()" class="adf-form-title" [matTooltip]="form.taskName">
{{form.taskName}}
<ng-container *ngIf="!form.taskName">
{{'FORM.FORM_RENDERER.NAMELESS_TASK' | translate}}
</ng-container>
</span>
<div *ngIf="hasForm()" class="adf-cloud-form-container adf-cloud-form-{{displayMode?.toLowerCase() || 'inline'}}-container">
<div class="adf-cloud-form-content"
[cdkTrapFocus]="displayMode === 'fullScreen'"
cdkTrapFocusAutoCapture>
</h4>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<adf-form-renderer [formDefinition]="form">
</adf-form-renderer>
</mat-card-content>
<mat-card-actions *ngIf="form.hasOutcomes()" class="adf-form-mat-card-actions">
<ng-content select="adf-cloud-form-custom-outcomes"></ng-content>
<ng-container *ngFor="let outcome of form.outcomes">
<button *ngIf="outcome.isVisible" [id]="'adf-form-'+ outcome.name | formatSpace" [color]="getColorForOutcome(outcome.name)"
mat-button [disabled]="!isOutcomeButtonEnabled(outcome)"
[class.adf-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
(click)="onOutcomeClicked(outcome)">
{{outcome.name | translate | uppercase }}
<adf-toolbar class="adf-cloud-form-toolbar" *ngIf="displayMode === 'fullScreen' && findDisplayConfiguration('fullScreen')?.options?.displayToolbar">
<div class="adf-cloud-form__form-title">
<span class="adf-cloud-form__display-name" [matTooltip]="form.taskName">
{{form.taskName}}
<ng-container *ngIf="!form.taskName">
{{'FORM.FORM_RENDERER.NAMELESS_TASK' | translate}}
</ng-container>
</span>
</div>
<adf-toolbar-divider></adf-toolbar-divider>
<button class="adf-cloud-form-close-button"
data-automation-id="adf-toolbar-right-back"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.CLOSE' | translate"
[attr.data-automation-id]="'adf-cloud-form-close-button'"
[matTooltip]="'ADF_VIEWER.ACTIONS.CLOSE' | translate"
mat-icon-button
title="{{ 'ADF_VIEWER.ACTIONS.CLOSE' | translate }}"
(click)="switchToDisplayMode()">
<mat-icon>close</mat-icon>
</button>
</ng-container>
</mat-card-actions>
</mat-card>
</adf-toolbar>
<mat-card [class.adf-cloud-form-content-card]="displayMode === 'fullScreen'">
<div class="adf-cloud-form-content-card-container">
<mat-card-header *ngIf="showTitle || showRefreshButton || showValidationIcon">
<mat-card-title>
<h4>
<div *ngIf="showValidationIcon" class="adf-form-validation-button">
<i id="adf-valid-form-icon" class="material-icons"
*ngIf="form.isValid; else no_valid_form">check_circle</i>
<ng-template #no_valid_form>
<i id="adf-invalid-form-icon" class="material-icons adf-invalid-color">error</i>
</ng-template>
</div>
<div *ngIf="displayMode !== 'fullScreen' && findDisplayConfiguration('fullScreen')" class="adf-cloud-form-fullscreen-button">
<button mat-icon-button (click)="switchToDisplayMode('fullScreen')" [attr.data-automation-id]="'adf-cloud-form-fullscreen-button'">
<mat-icon>fullscreen</mat-icon>
</button>
</div>
<div *ngIf="showRefreshButton" class="adf-cloud-form-reload-button" [matTooltip]="'ADF_VIEWER.ACTIONS.FULLSCREEN' | translate">
<button mat-icon-button (click)="onRefreshClicked()" [attr.aria-label]="'ADF_VIEWER.ACTIONS.FULLSCREEN' | translate">
<mat-icon>refresh</mat-icon>
</button>
</div>
<span *ngIf="isTitleEnabled()" class="adf-cloud-form-title" [matTooltip]="form.taskName">
{{form.taskName}}
<ng-container *ngIf="!form.taskName">
{{'FORM.FORM_RENDERER.NAMELESS_TASK' | translate}}
</ng-container>
</span>
</h4>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<adf-form-renderer [formDefinition]="form">
</adf-form-renderer>
</mat-card-content>
<mat-card-actions *ngIf="form.hasOutcomes()" class="adf-form-mat-card-actions">
<ng-content select="adf-cloud-form-custom-outcomes"></ng-content>
<ng-container *ngFor="let outcome of form.outcomes">
<button *ngIf="outcome.isVisible" [id]="'adf-form-'+ outcome.name | formatSpace" [color]="getColorForOutcome(outcome.name)"
mat-button [disabled]="!isOutcomeButtonEnabled(outcome)"
[class.adf-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
(click)="onOutcomeClicked(outcome)">
{{outcome.name | translate | uppercase }}
</button>
</ng-container>
</mat-card-actions>
</div>
</mat-card>
</div>
</div>

View File

@@ -0,0 +1,100 @@
/* cspell: disable-next-line */
/* stylelint-disable scss/at-extend-no-missing-placeholder */
.adf-full-screen {
width: 100%;
height: 100%;
background-color: var(--adf-theme-background-card-color);
}
.adf-cloud-form {
&-container {
.adf-cloud-form-content {
@extend .adf-full-screen;
flex: 1;
flex-direction: column;
display: flex;
}
}
&-fullscreen-container {
.adf-cloud-form-content {
position: fixed;
top: 0;
left: 0;
z-index: 100000;
}
}
&-inline-container {
@extend .adf-full-screen;
}
&-toolbar {
/* stylelint-disable-next-line selector-class-pattern */
.mat-toolbar {
background-color: var(--adf-theme-background-card-color-087);
}
}
&-fullscreen-button {
position: absolute;
right: 70px;
top: 30px;
}
&__display-name {
font-size: var(--theme-subheading-2-font-size);
opacity: 0.87;
line-height: 1.5;
letter-spacing: -0.4px;
font-weight: normal;
font-style: normal;
font-stretch: normal;
max-width: 400px;
text-overflow: ellipsis;
overflow: hidden;
display: inline-block;
vertical-align: middle;
color: var(--adf-theme-foreground-text-color);
}
&__form-title {
text-align: center;
flex: 1 1 auto;
}
&-content-card {
padding: 0;
height: 100%;
width: 100%;
.adf-cloud-form-content-card-container {
display: flex;
flex-direction: column;
height: 100%;
mat-card-content {
height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
mat-card-actions {
display: flex;
justify-content: flex-end;
}
}
}
&-sidebars {
display: flex;
flex: 1 1 auto;
adf-viewer-render {
order: 1;
flex: 1 1 auto;
display: flex;
}
}
}

View File

@@ -52,9 +52,12 @@ import {
} from '../mocks/cloud-form.mock';
import { FormCloudRepresentation } from '../models/form-cloud-representation.model';
import { FormCloudService } from '../services/form-cloud.service';
import { CloudFormRenderingService } from './cloud-form-rendering.service';
import { DisplayModeService } from '../services/display-mode.service';
import { FormCloudComponent } from './form-cloud.component';
import { ProcessServicesCloudModule } from '../../process-services-cloud.module';
import { MatButtonHarness } from '@angular/material/button/testing';
import { FormCloudDisplayMode } from '../../services/form-fields.interfaces';
import { CloudFormRenderingService } from './cloud-form-rendering.service';
const mockOauth2Auth: any = {
oauth2Auth: {
@@ -71,6 +74,7 @@ describe('FormCloudComponent', () => {
let matDialog: MatDialog;
let visibilityService: WidgetVisibilityService;
let formRenderingService: CloudFormRenderingService;
let displayModeService: DisplayModeService;
let documentRootLoader: HarnessLoader;
@Component({
@@ -111,6 +115,7 @@ describe('FormCloudComponent', () => {
formRenderingService = TestBed.inject(CloudFormRenderingService);
formCloudService = TestBed.inject(FormCloudService);
displayModeService = TestBed.inject(DisplayModeService);
matDialog = TestBed.inject(MatDialog);
@@ -1140,6 +1145,224 @@ describe('FormCloudComponent', () => {
});
});
describe('Full screen', async () => {
let displayModeOnSpy: jasmine.Spy;
let displayModeOffSpy: jasmine.Spy;
/**
* Helper function for loading the form in the tests
*
* @param form The form model to be loaded
*/
async function loadForm(form?: any): Promise<void> {
formComponent.ngOnChanges({ form: { currentValue: formComponent.parseForm(form || {}), firstChange: true, isFirstChange: () => true, previousValue: undefined } });
await fixture.whenStable();
fixture.detectChanges();
}
beforeEach(async () => {
displayModeOnSpy = spyOn(formComponent.displayModeOn, 'emit').and.stub();
displayModeOffSpy = spyOn(formComponent.displayModeOff, 'emit').and.stub();
spyOn(displayModeService, 'getDefaultDisplayModeConfigurations').and.callFake(() => DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS);
formComponent.taskId = 'any';
formComponent.appName = 'any';
formComponent.showRefreshButton = false;
formComponent.showTitle = false;
formComponent.showValidationIcon = false;
await loadForm();
displayModeOnSpy.calls.reset();
displayModeOffSpy.calls.reset();
});
it('should emit display mode turned on wit the inline configuration', async () => {
await loadForm();
expect(displayModeOnSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[0]);
});
it('should not be in fullScreen mode by default', () => {
const fullScreenModeContainer = fixture.debugElement.query(By.css('.adf-cloud-form-container.adf-cloud-form-fullscreen-container'));
const inlineModeContainer = fixture.debugElement.query(By.css('.adf-cloud-form-container.adf-cloud-form-inline-container'));
expect(fullScreenModeContainer).toBeNull();
expect(inlineModeContainer).not.toBeNull();
});
it('should be in fullScreen mode if it is forced', async () => {
await loadForm({ displayMode: FormCloudDisplayMode.fullScreen });
const fullScreenModeContainer = fixture.debugElement.query(By.css('.adf-cloud-form-container.adf-cloud-form-fullscreen-container'));
const inlineModeContainer = fixture.debugElement.query(By.css('.adf-cloud-form-container.adf-cloud-form-inline-container'));
expect(fullScreenModeContainer).not.toBeNull();
expect(inlineModeContainer).toBeNull();
});
it('should display full screen button on header when header is displayed an not in fullScreen mode', () => {
formComponent.showTitle = true;
fixture.detectChanges();
const fullScreenButton = fixture.debugElement.query(By.css('.adf-cloud-form-fullscreen-button'));
expect(fullScreenButton).not.toBeNull();
});
it('should set fullScreen mode on clicking on the full screen button', async () => {
formComponent.showTitle = true;
fixture.detectChanges();
const fullScreenButton = await documentRootLoader.getHarness(
MatButtonHarness.with({ selector: `[data-automation-id="adf-cloud-form-fullscreen-button"]` })
);
await fullScreenButton.click();
await fixture.whenStable();
expect(formComponent.displayMode).toBe(FormCloudDisplayMode.fullScreen);
expect(displayModeOffSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[0]);
expect(displayModeOnSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1]);
});
it('should not display full screen button on header when header is displayed but in fullScreen mode', async () => {
formComponent.showTitle = true;
await loadForm({ displayMode: FormCloudDisplayMode.fullScreen });
const fullScreenButton = fixture.debugElement.query(By.css('.adf-cloud-form-fullscreen-button'));
expect(fullScreenButton).toBeNull();
});
it('should not display full screen button when header is not displayed', () => {
const fullScreenButton = fixture.debugElement.query(By.css('.adf-cloud-form-fullscreen-button'));
expect(fullScreenButton).toBeNull();
});
it('should not set the styles for the card', () => {
const fullScreenCard = fixture.debugElement.query(By.css('.adf-cloud-form-content-card'));
expect(fullScreenCard).toBeNull();
});
it('should set fullScreen mode from the form service notification', () => {
DisplayModeService.changeDisplayMode({ displayMode: FormCloudDisplayMode.fullScreen, id: formComponent.id });
expect(displayModeOffSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[0]);
expect(displayModeOnSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1]);
expect(formComponent.displayMode).toBe(FormCloudDisplayMode.fullScreen);
displayModeOnSpy.calls.reset();
displayModeOffSpy.calls.reset();
DisplayModeService.changeDisplayMode({ displayMode: FormCloudDisplayMode.inline, id: formComponent.id });
expect(displayModeOffSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1]);
expect(displayModeOnSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[0]);
expect(formComponent.displayMode).toBe(FormCloudDisplayMode.inline);
});
it('should not change the display mode when the notification change is from a different id', () => {
DisplayModeService.changeDisplayMode({ displayMode: FormCloudDisplayMode.fullScreen, id: formComponent.id });
expect(formComponent.displayMode).toBe(FormCloudDisplayMode.fullScreen);
expect(displayModeOffSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[0]);
expect(displayModeOnSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1]);
displayModeOnSpy.calls.reset();
displayModeOffSpy.calls.reset();
DisplayModeService.changeDisplayMode({ displayMode: FormCloudDisplayMode.inline, id: 'otherId' });
expect(displayModeOffSpy).not.toHaveBeenCalled();
expect(displayModeOnSpy).not.toHaveBeenCalled();
expect(formComponent.displayMode).toBe(FormCloudDisplayMode.fullScreen);
});
describe('fullScreen Mode', () => {
beforeEach(async () => {
await loadForm({ displayMode: FormCloudDisplayMode.fullScreen });
});
it('should emit display mode turned on wit the fullScreen configuration', () => {
expect(displayModeOnSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1]);
});
it('should display the toolbar with the nameless task title when the task name is not provided and toolbar is enabled', () => {
const cloudFormToolbarDisplayName: HTMLSpanElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-form__display-name');
expect(cloudFormToolbarDisplayName).not.toBeNull();
expect(cloudFormToolbarDisplayName.textContent.trim()).toEqual('Nameless task');
});
it('should display the toolbar with the task title when the task name is provided and toolbar is enabled', async () => {
const taskName = 'task-name';
await loadForm({ displayMode: FormCloudDisplayMode.fullScreen, taskName });
const cloudFormToolbarDisplayName: HTMLSpanElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-form__display-name');
expect(cloudFormToolbarDisplayName).not.toBeNull();
expect(cloudFormToolbarDisplayName.textContent.trim()).toEqual(taskName);
});
it('should not display the toolbar with the task title when the toolbar option is disabled', async () => {
formComponent.displayModeConfigurations = [
{
displayMode: FormCloudDisplayMode.fullScreen,
options: {
onCompleteTask: () => { },
onDisplayModeOff: () => { },
onDisplayModeOn: () => { },
onSaveTask: () => { },
displayToolbar: false
}
}
];
await loadForm({ displayMode: FormCloudDisplayMode.fullScreen });
const cloudFormToolbar: HTMLSpanElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-form-toolbar');
expect(cloudFormToolbar).toBeNull();
});
it('should set the styles for the card', () => {
const fullScreenCard = fixture.debugElement.query(By.css('.adf-cloud-form-content-card'));
expect(fullScreenCard).not.toBeNull();
});
it('should close fullScreen when close button is clicked', async () => {
displayModeOnSpy.calls.reset();
displayModeOffSpy.calls.reset();
const closeButton = await documentRootLoader.getHarness(
MatButtonHarness.with({ selector: `[data-automation-id="adf-cloud-form-close-button"]` })
);
await closeButton.click();
await fixture.whenStable();
fixture.detectChanges();
expect(formComponent.displayMode).toEqual(FormCloudDisplayMode.inline);
expect(displayModeOffSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1]);
expect(displayModeOnSpy).toHaveBeenCalledWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[0]);
});
it('should close fullScreen when completing the task', () => {
const formRenderingServiceOnCompleteTaskSpy = spyOn(displayModeService, 'onCompleteTask').and.callThrough();
const onCompleteTaskSpy = spyOn(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1].options, 'onCompleteTask').and.callThrough();
const formRenderingServiceChangeDisplayModeSpy = spyOn(DisplayModeService, 'changeDisplayMode').and.callThrough();
displayModeOnSpy.calls.reset();
displayModeOffSpy.calls.reset();
formComponent.completeTaskForm();
expect(formRenderingServiceOnCompleteTaskSpy).toHaveBeenCalledOnceWith(formComponent.id, FormCloudDisplayMode.fullScreen, DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS);
expect(onCompleteTaskSpy).toHaveBeenCalledOnceWith(formComponent.id);
expect(formRenderingServiceChangeDisplayModeSpy).toHaveBeenCalledOnceWith({ id: formComponent.id, displayMode: FormCloudDisplayMode.inline });
expect(displayModeOffSpy).toHaveBeenCalledOnceWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1]);
expect(displayModeOnSpy).toHaveBeenCalledOnceWith(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[0]);
expect(formComponent.displayMode).toBe(FormCloudDisplayMode.inline);
});
});
});
});
describe('Multilingual Form', () => {

View File

@@ -15,9 +15,9 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnDestroy, HostListener } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnDestroy, HostListener, OnInit } from '@angular/core';
import { Observable, of, forkJoin, Subject, Subscription } from 'rxjs';
import { switchMap, takeUntil, map } from 'rxjs/operators';
import { switchMap, takeUntil, map, filter } from 'rxjs/operators';
import {
FormBaseComponent,
FormFieldModel,
@@ -38,12 +38,16 @@ import { TaskVariableCloud } from '../models/task-variable-cloud.model';
import { TaskDetailsCloudModel } from '../../task/start-task/models/task-details-cloud.model';
import { MatDialog } from '@angular/material/dialog';
import { ConfirmDialogComponent } from '@alfresco/adf-content-services';
import { v4 as uuidGeneration } from 'uuid';
import { FormCloudDisplayMode, FormCloudDisplayModeConfiguration } from '../../services/form-fields.interfaces';
import { DisplayModeService } from '../public-api';
@Component({
selector: 'adf-cloud-form',
templateUrl: './form-cloud.component.html'
templateUrl: './form-cloud.component.html',
styleUrls: ['./form-cloud.component.scss']
})
export class FormCloudComponent extends FormBaseComponent implements OnChanges, OnDestroy {
export class FormCloudComponent extends FormBaseComponent implements OnChanges, OnInit, OnDestroy {
/** App name to fetch corresponding form and values. */
@Input()
appName: string = '';
@@ -76,6 +80,12 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
@Input()
fieldValidators: FormFieldValidator[] = [...FORM_FIELD_VALIDATORS];
/**
* The available display configurations for the form
*/
@Input()
displayModeConfigurations: FormCloudDisplayModeConfiguration[];
/** Emitted when the form is submitted with the `Save` or custom outcomes. */
@Output()
formSaved = new EventEmitter<FormModel>();
@@ -96,19 +106,32 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
@Output()
formContentClicked = new EventEmitter<ContentLinkModel>();
/** Emitted when a display mode configuration is turned on. */
@Output()
displayModeOn = new EventEmitter<FormCloudDisplayModeConfiguration>();
/** Emitted when a display mode configuration is turned off. */
@Output()
displayModeOff = new EventEmitter<FormCloudDisplayModeConfiguration>();
protected subscriptions: Subscription[] = [];
nodeId: string;
formCloudRepresentationJSON: any;
protected onDestroy$ = new Subject<boolean>();
readonly id: string;
displayMode: FormCloudDisplayMode;
constructor(
protected formCloudService: FormCloudService,
protected formService: FormService,
private dialog: MatDialog,
protected visibilityService: WidgetVisibilityService
protected visibilityService: WidgetVisibilityService,
private readonly displayModeService: DisplayModeService
) {
super();
this.id = uuidGeneration();
this.formService.formContentClicked.pipe(takeUntil(this.onDestroy$)).subscribe((content) => {
if (content instanceof UploadWidgetContentLinkModel) {
@@ -166,6 +189,36 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
this.refreshFormData();
return;
}
const formRepresentation = changes['form'];
if (formRepresentation?.currentValue) {
this.form = formRepresentation.currentValue;
this.onFormLoaded(this.form);
return;
}
}
ngOnInit(): void {
DisplayModeService.displayMode$
.pipe(
filter(change => change.id === this.id),
takeUntil(this.onDestroy$)
).subscribe((displayModeChange) => {
const oldDisplayMode = this.displayMode;
this.displayMode = displayModeChange.displayMode;
const oldDisplayModeConfiguration = this.displayModeService.findConfiguration(oldDisplayMode, this.displayModeConfigurations);
const newDisplayModeConfiguration = this.displayModeService.findConfiguration(displayModeChange.displayMode, this.displayModeConfigurations);
if (oldDisplayModeConfiguration?.displayMode !== newDisplayModeConfiguration?.displayMode) {
if (oldDisplayModeConfiguration) {
this.displayModeOff.emit(oldDisplayModeConfiguration);
}
if (newDisplayModeConfiguration) {
this.displayModeOn.emit(newDisplayModeConfiguration);
}
}
});
}
/**
@@ -264,6 +317,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
},
(error) => this.onTaskSavedError(error)
);
this.displayModeService.onSaveTask(this.id, this.displayMode, this.displayModeConfigurations);
}
}
@@ -284,6 +338,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
} else {
this.completeForm(outcome);
}
this.displayModeService.onCompleteTask(this.id, this.displayMode, this.displayModeConfigurations);
}
private completeForm(outcome?: string) {
@@ -342,6 +397,9 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
}
protected onFormLoaded(form: FormModel) {
this.displayModeConfigurations = this.displayModeService.getDisplayModeConfigurations(this.displayModeConfigurations);
this.displayMode = this.displayModeService.switchToDisplayMode(this.id, this.form.json.displayMode, this.displayMode, this.displayModeConfigurations);
this.displayModeOn.emit(this.displayModeService.findConfiguration(this.displayMode, this.displayModeConfigurations));
this.formLoaded.emit(form);
}
@@ -376,10 +434,18 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
return !args.defaultPrevented;
}
protected storeFormAsMetadata() {}
protected storeFormAsMetadata() { }
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
switchToDisplayMode(newDisplayMode?: string) {
this.displayModeService.switchToDisplayMode(this.id, FormCloudDisplayMode[newDisplayMode], this.displayMode, this.displayModeConfigurations);
}
findDisplayConfiguration(displayMode?: string): FormCloudDisplayModeConfiguration {
return this.displayModeService.findConfiguration(FormCloudDisplayMode[displayMode], this.displayModeConfigurations);
}
}

View File

@@ -17,7 +17,7 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CoreModule } from '@alfresco/adf-core';
import { CoreModule, ToolbarModule } from '@alfresco/adf-core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MaterialModule } from '../material.module';
import { FormCloudComponent } from './components/form-cloud.component';
@@ -41,6 +41,7 @@ import { FilePropertiesTableCloudComponent } from './components/widgets/attach-f
import { FileViewerWidgetComponent } from './components/widgets/file-viewer/file-viewer.widget';
import { DisplayRichTextWidgetComponent } from './components/widgets/display-rich-text/display-rich-text.widget';
import { RichTextEditorModule } from '../rich-text-editor';
import { A11yModule } from '@angular/cdk/a11y';
@NgModule({
imports: [
@@ -55,7 +56,9 @@ import { RichTextEditorModule } from '../rich-text-editor';
ContentMetadataModule,
UploadModule,
AlfrescoViewerModule,
RichTextEditorModule
RichTextEditorModule,
ToolbarModule,
A11yModule
],
declarations: [
FormCloudComponent,

View File

@@ -38,5 +38,6 @@ export * from './services/form-cloud.service';
export * from './services/form-definition-selector-cloud.service';
export * from './services/content-cloud-node-selector.service';
export * from './services/process-cloud-content.service';
export * from './services/display-mode.service';
export * from './form-cloud.module';

View File

@@ -0,0 +1,164 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormCloudDisplayMode, FormCloudDisplayModeConfiguration } from '../../services/form-fields.interfaces';
import { DisplayModeService } from './display-mode.service';
describe('DisplayModeService', () => {
let service: DisplayModeService;
let displayModeOnSpy: jasmine.Spy;
let displayModeOffSpy: jasmine.Spy;
let completeTaskSpy: jasmine.Spy;
let saveTaskSpy: jasmine.Spy;
let changeDisplayModeSpy: jasmine.Spy;
const formId = 'id';
beforeEach(() => {
service = new DisplayModeService();
spyOn(service, 'getDefaultDisplayModeConfigurations').and.callFake(() => DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS);
displayModeOnSpy = spyOn(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1].options, 'onDisplayModeOn');
displayModeOffSpy = spyOn(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1].options, 'onDisplayModeOff');
completeTaskSpy = spyOn(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1].options, 'onCompleteTask');
saveTaskSpy = spyOn(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1].options, 'onSaveTask');
changeDisplayModeSpy = spyOn(DisplayModeService, 'changeDisplayMode');
});
it('should return the default display mode configurations when no available configurations are provided', () => {
expect(service.getDisplayModeConfigurations()).toBe(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS);
expect(service.getDisplayModeConfigurations(null)).toBe(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS);
expect(service.getDisplayModeConfigurations([])).toBe(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS);
});
it('should return the provided display mode configurations when available configurations are provided', () => {
const availableConfigurations: FormCloudDisplayModeConfiguration[] = [
{
displayMode: FormCloudDisplayMode.fullScreen
}
];
expect(service.getDisplayModeConfigurations(availableConfigurations)).toBe(availableConfigurations);
});
it('should return the default display mode when no display mode is provided', () => {
expect(service.getDisplayMode()).toBe(DisplayModeService.DEFAULT_DISPLAY_MODE);
expect(service.getDisplayMode(null)).toBe(DisplayModeService.DEFAULT_DISPLAY_MODE);
});
it('should return the default display mode when no display mode does not exist in the configuration', () => {
expect(service.getDisplayMode('notExisting' as any)).toBe(DisplayModeService.DEFAULT_DISPLAY_MODE);
expect(service.getDisplayMode('notExisting' as any, [])).toBe(DisplayModeService.DEFAULT_DISPLAY_MODE);
expect(service.getDisplayMode('notExisting' as any, [{ displayMode: FormCloudDisplayMode.fullScreen }])).toBe(FormCloudDisplayMode.fullScreen);
expect(service.getDisplayMode('notExisting' as any, [{ displayMode: FormCloudDisplayMode.fullScreen }, { displayMode: FormCloudDisplayMode.inline }])).toBe(FormCloudDisplayMode.fullScreen);
expect(service.getDisplayMode('notExisting' as any, [{ displayMode: FormCloudDisplayMode.fullScreen, default: true }, { displayMode: FormCloudDisplayMode.inline }])).toBe(FormCloudDisplayMode.fullScreen);
});
it('should return the provided display mode when display mode is provided', () => {
expect(service.getDisplayMode(FormCloudDisplayMode.fullScreen)).toBe(FormCloudDisplayMode.fullScreen);
});
it('should find the display configuration', () => {
expect(service.findConfiguration()).toBeUndefined();
expect(service.findConfiguration(FormCloudDisplayMode.fullScreen)).toBe(DisplayModeService.IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS[1]);
expect(service.findConfiguration('notExisting' as any)).toBeUndefined();
expect(service.findConfiguration(FormCloudDisplayMode.fullScreen, [{ displayMode: FormCloudDisplayMode.fullScreen }])).toEqual({ displayMode: FormCloudDisplayMode.fullScreen });
expect(service.findConfiguration(FormCloudDisplayMode.fullScreen, [{ displayMode: FormCloudDisplayMode.inline }])).toBeUndefined();
});
it('should call the display mode on function in the configuration when it is found', () => {
service.onDisplayModeOn(formId, FormCloudDisplayMode.fullScreen);
expect(displayModeOnSpy).toHaveBeenCalledWith(formId);
});
it('should not call the on display mode on function in the configuration when it is not found', () => {
service.onDisplayModeOff(formId, FormCloudDisplayMode.inline);
expect(displayModeOnSpy).not.toHaveBeenCalled();
});
it('should call the display mode off function in the configuration when it is found', () => {
service.onDisplayModeOff(formId, FormCloudDisplayMode.fullScreen);
expect(displayModeOffSpy).toHaveBeenCalledWith(formId);
});
it('should not call the on display mode off function in the configuration when it is not found', () => {
service.onDisplayModeOff(formId, FormCloudDisplayMode.inline);
expect(displayModeOffSpy).not.toHaveBeenCalled();
});
it('should call the complete task function in the configuration when it is found', () => {
service.onCompleteTask(formId, FormCloudDisplayMode.fullScreen);
expect(completeTaskSpy).toHaveBeenCalledWith(formId);
});
it('should not call the complete task function in the configuration when it is not found', () => {
service.onCompleteTask(formId, FormCloudDisplayMode.inline);
expect(completeTaskSpy).not.toHaveBeenCalled();
});
it('should call the save task function in the configuration when it is found', () => {
service.onSaveTask(formId, FormCloudDisplayMode.fullScreen);
expect(saveTaskSpy).toHaveBeenCalledWith(formId);
});
it('should not call the save task function in the configuration when it is not found', () => {
service.onSaveTask(formId, FormCloudDisplayMode.inline);
expect(saveTaskSpy).not.toHaveBeenCalled();
});
it('should return the provided display mode when calling the switchToDisplayMode and display exists in configuration', () => {
expect(service.switchToDisplayMode(formId, FormCloudDisplayMode.fullScreen)).toBe(FormCloudDisplayMode.fullScreen);
expect(changeDisplayModeSpy).toHaveBeenCalledWith({ id: formId, displayMode: FormCloudDisplayMode.fullScreen });
});
it('should return the default display mode when calling the switchToDisplayMode and display does not exist in configuration', () => {
expect(service.switchToDisplayMode(formId, 'notExisting' as any)).toBe(DisplayModeService.DEFAULT_DISPLAY_MODE);
});
it('should not call the change display mode method when switchToDisplayMode and the mode to switch is the same as the old one', () => {
expect(service.switchToDisplayMode(formId, FormCloudDisplayMode.fullScreen, FormCloudDisplayMode.fullScreen)).toBe(FormCloudDisplayMode.fullScreen);
expect(changeDisplayModeSpy).not.toHaveBeenCalledWith();
});
it('should not call the change display mode method when switchToDisplayMode and the mode to switch does not exist and the previous mode was the default one', () => {
expect(service.switchToDisplayMode(formId, 'notExisting' as any, DisplayModeService.DEFAULT_DISPLAY_MODE)).toBe(DisplayModeService.DEFAULT_DISPLAY_MODE);
expect(changeDisplayModeSpy).not.toHaveBeenCalledWith();
});
it('should not call the change display mode method when switchToDisplayMode and the mode to switch does not exist and the previous mode was not provided', () => {
expect(service.switchToDisplayMode(formId, 'notExisting' as any, DisplayModeService.DEFAULT_DISPLAY_MODE)).toBe(DisplayModeService.DEFAULT_DISPLAY_MODE);
expect(changeDisplayModeSpy).not.toHaveBeenCalledWith();
});
it('should call the display mode off when switching the display mode and the old display mode is provided and it provides the off method', () => {
expect(service.switchToDisplayMode(formId, FormCloudDisplayMode.inline, FormCloudDisplayMode.fullScreen)).toBe(FormCloudDisplayMode.inline);
expect(displayModeOffSpy).toHaveBeenCalled();
});
it('should call the display mode on when switching the display mode and the new display mode provides the on method', () => {
expect(service.switchToDisplayMode(formId, FormCloudDisplayMode.fullScreen)).toBe(FormCloudDisplayMode.fullScreen);
expect(displayModeOnSpy).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,147 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { FormCloudDisplayMode, FormCloudDisplayModeChange, FormCloudDisplayModeConfiguration } from '../../services/form-fields.interfaces';
@Injectable({
providedIn: 'root'
})
export class DisplayModeService {
public static readonly DEFAULT_DISPLAY_MODE_CONFIGURATIONS: FormCloudDisplayModeConfiguration[] = [
{
displayMode: FormCloudDisplayMode.inline,
default: true
}
];
public static readonly IMPLEMENTED_DISPLAY_MODE_CONFIGURATIONS: FormCloudDisplayModeConfiguration[] = [
...DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS,
{
displayMode: FormCloudDisplayMode.fullScreen,
options: {
onDisplayModeOn: () => { },
onDisplayModeOff: (id: string) => DisplayModeService.changeDisplayMode({ displayMode: FormCloudDisplayMode.inline, id }),
onCompleteTask: (id: string) => DisplayModeService.changeDisplayMode({ displayMode: FormCloudDisplayMode.inline, id }),
onSaveTask: () => { },
displayToolbar: true
}
}
];
public static readonly DEFAULT_DISPLAY_MODE: FormCloudDisplayMode = FormCloudDisplayMode.inline;
private static readonly displayMode = new Subject<FormCloudDisplayModeChange>();
static readonly displayMode$: Observable<FormCloudDisplayModeChange> = DisplayModeService.displayMode.asObservable();
static changeDisplayMode(change: FormCloudDisplayModeChange) {
DisplayModeService.displayMode.next(change);
}
getDefaultDisplayModeConfigurations(): FormCloudDisplayModeConfiguration[] {
return DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS;
}
getDisplayModeConfigurations(availableConfigurations?: FormCloudDisplayModeConfiguration[]): FormCloudDisplayModeConfiguration[] {
if (availableConfigurations && availableConfigurations.length > 0) {
return availableConfigurations;
} else {
return this.getDefaultDisplayModeConfigurations();
}
}
getDisplayMode(displayMode?: FormCloudDisplayMode, availableConfigurations?: FormCloudDisplayModeConfiguration[]): FormCloudDisplayMode {
const configuration = this.findConfiguration(displayMode, availableConfigurations);
if (configuration) {
return configuration.displayMode;
} else if (availableConfigurations && availableConfigurations.length > 0) {
return availableConfigurations.length === 1 ?
availableConfigurations[0].displayMode :
(availableConfigurations.find(config => config.default)?.displayMode || availableConfigurations[0].displayMode);
} else {
return DisplayModeService.DEFAULT_DISPLAY_MODE;
}
}
findConfiguration(displayMode?: FormCloudDisplayMode, availableConfigurations?: FormCloudDisplayModeConfiguration[]): FormCloudDisplayModeConfiguration {
return this.getDisplayModeConfigurations(availableConfigurations).find(config => config.displayMode === displayMode);
}
onCompleteTask(id?: string, displayMode?: FormCloudDisplayMode, availableConfigurations?: FormCloudDisplayModeConfiguration[]) {
const configuration = this.findConfiguration(displayMode, availableConfigurations);
if (configuration?.options?.onCompleteTask) {
configuration.options.onCompleteTask(id);
}
}
onSaveTask(id?: string, displayMode?: FormCloudDisplayMode, availableConfigurations?: FormCloudDisplayModeConfiguration[]) {
const configuration = this.findConfiguration(displayMode, availableConfigurations);
if (configuration?.options?.onSaveTask) {
configuration.options.onSaveTask(id);
}
}
onDisplayModeOff(id?: string, displayMode?: FormCloudDisplayMode, availableConfigurations?: FormCloudDisplayModeConfiguration[]) {
const configuration = this.findConfiguration(displayMode, availableConfigurations);
if (configuration?.options?.onDisplayModeOff) {
configuration.options.onDisplayModeOff(id);
}
}
onDisplayModeOn(id?: string, displayMode?: FormCloudDisplayMode, availableConfigurations?: FormCloudDisplayModeConfiguration[]) {
const configuration = this.findConfiguration(displayMode, availableConfigurations);
if (configuration?.options?.onDisplayModeOn) {
configuration.options.onDisplayModeOn(id);
}
}
switchToDisplayMode(
id?: string,
newDisplayMode?: FormCloudDisplayMode,
oldDisplayMode?: FormCloudDisplayMode,
availableConfigurations?: FormCloudDisplayModeConfiguration[]
): FormCloudDisplayMode {
const oldConfiguration = this.findConfiguration(oldDisplayMode, availableConfigurations);
const newConfiguration = this.findConfiguration(newDisplayMode, availableConfigurations);
if (oldConfiguration?.displayMode !== newConfiguration?.displayMode) {
if (oldConfiguration) {
this.onDisplayModeOff(id, oldDisplayMode, availableConfigurations);
}
if (newConfiguration) {
DisplayModeService.changeDisplayMode({ id, displayMode: newConfiguration.displayMode });
this.onDisplayModeOn(id, newDisplayMode, availableConfigurations);
return newConfiguration.displayMode;
} else {
const displayMode = this.getDisplayMode(newDisplayMode, availableConfigurations);
DisplayModeService.changeDisplayMode({ id, displayMode });
this.onDisplayModeOn(id, displayMode, availableConfigurations);
return displayMode;
}
} else {
return oldConfiguration?.displayMode || this.getDisplayMode(oldDisplayMode, availableConfigurations);
}
}
}

View File

@@ -26,6 +26,7 @@ export interface FormRepresentation {
version?: number;
formDefinition?: FormDefinition;
standAlone?: boolean;
displayMode?: FormCloudDisplayMode;
}
export interface FormTab {
@@ -59,7 +60,7 @@ export interface Container {
}
export type FormFieldRepresentation = (DateField | DateTimeField | TextField | AttachFileField | DropDownField |
RadioField | TypeaheadField | PeopleField | AmountField | NumberField | CheckboxField | HyperlinkField );
RadioField | TypeaheadField | PeopleField | AmountField | NumberField | CheckboxField | HyperlinkField);
export interface AttachFileField extends FormField {
required: boolean;
@@ -233,3 +234,28 @@ export enum FormFieldType {
displayText = 'readonly-text',
fileViewer = 'file-viewer'
}
export interface FormCloudDisplayModeConfigurationOptions {
onCompleteTask(id?: string): void;
onSaveTask(id?: string): void;
onDisplayModeOn(id?: string): void;
onDisplayModeOff(id?: string): void;
[key: string]: any;
};
export interface FormCloudDisplayModeConfiguration {
displayMode: FormCloudDisplayMode;
options?: FormCloudDisplayModeConfigurationOptions;
default?: boolean;
};
// eslint-disable-next-line no-shadow
export enum FormCloudDisplayMode {
inline = 'inline',
fullScreen = 'fullScreen'
};
export interface FormCloudDisplayModeChange {
displayMode: FormCloudDisplayMode;
id?: string;
};

View File

@@ -1,5 +1,5 @@
<div *ngIf="!loading; else loadingTemplate">
<adf-cloud-form *ngIf="hasForm(); else withoutForm"
<adf-cloud-form #adfCloudForm *ngIf="hasForm(); else withoutForm"
[appName]="appName"
[appVersion]="taskDetails.appVersion"
[taskId]="taskId"
@@ -10,12 +10,15 @@
[showValidationIcon]="showValidationIcon"
[showCompleteButton]="canCompleteTask()"
[showSaveButton]="canCompleteTask()"
[displayModeConfigurations]="displayModeConfigurations"
(formSaved)="onFormSaved($event)"
(formCompleted)="onFormCompleted($event)"
(formError)="onError($event)"
(error)="onError($event)"
(formContentClicked)="onFormContentClicked($event)"
(executeOutcome)="onFormExecuteOutcome($event)">
(executeOutcome)="onFormExecuteOutcome($event)"
(displayModeOn)="onDisplayModeOn($event)"
(displayModeOff)="onDisplayModeOff($event)">
<adf-cloud-form-custom-outcomes>
<ng-template [ngTemplateOutlet]="taskFormCloudButtons">
</ng-template>

View File

@@ -36,6 +36,8 @@ import { IdentityUserService } from '../../../people/services/identity-user.serv
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing';
import { DisplayModeService } from '../../../form/services/display-mode.service';
import { FormCloudComponent } from '../../../form/components/form-cloud.component';
const taskDetails: TaskDetailsCloudModel = {
appName: 'simple-app',
@@ -66,7 +68,8 @@ describe('TaskFormCloudComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), ProcessServiceCloudTestingModule]
imports: [TranslateModule.forRoot(), ProcessServiceCloudTestingModule],
declarations: [FormCloudComponent]
});
taskDetails.status = TASK_ASSIGNED_STATE;
taskDetails.permissions = [TASK_VIEW_PERMISSION];
@@ -421,6 +424,26 @@ describe('TaskFormCloudComponent', () => {
fixture.detectChanges();
expect(component.onTaskLoaded.emit).toHaveBeenCalledWith(taskDetails);
});
it('should emit displayModeOn when display mode is turned on', async () => {
spyOn(component.displayModeOn, 'emit').and.stub();
component.onDisplayModeOn(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]);
fixture.detectChanges();
await fixture.whenStable();
expect(component.displayModeOn.emit).toHaveBeenCalledWith(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]);
});
it('should emit displayModeOff when display mode is turned on', async () => {
spyOn(component.displayModeOff, 'emit').and.stub();
component.onDisplayModeOff(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]);
fixture.detectChanges();
await fixture.whenStable();
expect(component.displayModeOff.emit).toHaveBeenCalledWith(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]);
});
});
it('should display task name as title on no form template if showTitle is true', () => {
@@ -452,4 +475,18 @@ describe('TaskFormCloudComponent', () => {
expect(noFormTemplateTitle).toBeNull();
});
it('should call children cloud task form change display mode when changing the display mode', () => {
const displayMode = 'displayMode';
component.taskDetails = { ...taskDetails, formKey: 'some-form' };
fixture.detectChanges();
expect(component.adfCloudForm).toBeDefined();
const switchToDisplayModeSpy = spyOn(component.adfCloudForm, 'switchToDisplayMode');
component.switchToDisplayMode(displayMode);
expect(switchToDisplayModeSpy).toHaveBeenCalledOnceWith(displayMode);
});
});

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnInit, ViewEncapsulation, OnDestroy, ViewChild } from '@angular/core';
import { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model';
import { TaskCloudService } from '../../services/task-cloud.service';
import { FormRenderingService, FormModel, ContentLinkModel, FormOutcomeEvent } from '@alfresco/adf-core';
@@ -24,6 +24,8 @@ import { DropdownCloudWidgetComponent } from '../../../form/components/widgets/d
import { DateCloudWidgetComponent } from '../../../form/components/widgets/date/date-cloud.widget';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { FormCloudDisplayModeConfiguration } from '../../../services/form-fields.interfaces';
import { FormCloudComponent } from '../../../form/components/form-cloud.component';
@Component({
selector: 'adf-cloud-task-form',
@@ -64,6 +66,12 @@ export class TaskFormCloudComponent implements OnInit, OnChanges, OnDestroy {
@Input()
readOnly = false;
/**
* The available display configurations for the form
*/
@Input()
displayModeConfigurations: FormCloudDisplayModeConfiguration[];
/** Emitted when the form is saved. */
@Output()
formSaved = new EventEmitter<FormModel>();
@@ -109,6 +117,17 @@ export class TaskFormCloudComponent implements OnInit, OnChanges, OnDestroy {
@Output()
onTaskLoaded = new EventEmitter<TaskDetailsCloudModel>(); /* eslint-disable-line */
/** Emitted when a display mode configuration is turned on. */
@Output()
displayModeOn = new EventEmitter<FormCloudDisplayModeConfiguration>();
/** Emitted when a display mode configuration is turned off. */
@Output()
displayModeOff = new EventEmitter<FormCloudDisplayModeConfiguration>();
@ViewChild('adfCloudForm', { static: false })
adfCloudForm: FormCloudComponent;
taskDetails: TaskDetailsCloudModel;
candidateUsers: string[] = [];
@@ -231,6 +250,18 @@ export class TaskFormCloudComponent implements OnInit, OnChanges, OnDestroy {
this.executeOutcome.emit(outcome);
}
switchToDisplayMode(newDisplayMode?: string) {
this.adfCloudForm.switchToDisplayMode(newDisplayMode);
}
onDisplayModeOn(displayModeConfiguration: FormCloudDisplayModeConfiguration){
this.displayModeOn.emit(displayModeConfiguration);
}
onDisplayModeOff(displayModeConfiguration: FormCloudDisplayModeConfiguration){
this.displayModeOff.emit(displayModeConfiguration);
}
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();