From dc88ef8ce74aaff6a4e7c2fde9b8f1a98fb01dae Mon Sep 17 00:00:00 2001 From: Pablo Martinez Date: Tue, 5 Mar 2024 15:10:50 +0100 Subject: [PATCH] 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 --- .../components/form-cloud.component.md | 38 +++ .../components/task-form-cloud.component.md | 38 +++ .../components/widgets/core/form.model.ts | 3 + .../form/components/form-cloud.component.html | 116 +++++---- .../form/components/form-cloud.component.scss | 100 ++++++++ .../components/form-cloud.component.spec.ts | 225 +++++++++++++++++- .../form/components/form-cloud.component.ts | 78 +++++- .../src/lib/form/form-cloud.module.ts | 7 +- .../src/lib/form/public-api.ts | 1 + .../services/display-mode.service.spec.ts | 164 +++++++++++++ .../lib/form/services/display-mode.service.ts | 147 ++++++++++++ .../lib/services/form-fields.interfaces.ts | 28 ++- .../components/task-form-cloud.component.html | 7 +- .../task-form-cloud.component.spec.ts | 39 ++- .../components/task-form-cloud.component.ts | 33 ++- 15 files changed, 969 insertions(+), 55 deletions(-) create mode 100644 lib/process-services-cloud/src/lib/form/components/form-cloud.component.scss create mode 100644 lib/process-services-cloud/src/lib/form/services/display-mode.service.spec.ts create mode 100644 lib/process-services-cloud/src/lib/form/services/display-mode.service.ts diff --git a/docs/process-services-cloud/components/form-cloud.component.md b/docs/process-services-cloud/components/form-cloud.component.md index fd9cb17cf1..da267752ac 100644 --- a/docs/process-services-cloud/components/form-cloud.component.md +++ b/docs/process-services-cloud/components/form-cloud.component.md @@ -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 + + + + +``` + +**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. diff --git a/docs/process-services-cloud/components/task-form-cloud.component.md b/docs/process-services-cloud/components/task-form-cloud.component.md index d7c83f8951..ac023098b8 100644 --- a/docs/process-services-cloud/components/task-form-cloud.component.md +++ b/docs/process-services-cloud/components/task-form-cloud.component.md @@ -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)`` | Emitted when the task is claimed. | | taskCompleted | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when the task is completed. | | taskUnclaimed | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | 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 + + + + +``` + +**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 diff --git a/lib/core/src/lib/form/components/widgets/core/form.model.ts b/lib/core/src/lib/form/components/widgets/core/form.model.ts index c98600bdf0..f6bf8c0f31 100644 --- a/lib/core/src/lib/form/components/widgets/core/form.model.ts +++ b/lib/core/src/lib/form/components/widgets/core/form.model.ts @@ -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)); diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.html b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.html index e633fcd3e6..2bc2a8c22e 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.html +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.html @@ -3,47 +3,81 @@ -
- - - -

-
- check_circle - - error - -
-
- -
- - {{form.taskName}} - - {{'FORM.FORM_RENDERER.NAMELESS_TASK' | translate}} - - +
+
-

-
-
- - - - - - - - - - -
+ + + +
+ + +

+
+ check_circle + + error + +
+
+ +
+
+ +
+ + {{form.taskName}} + + {{'FORM.FORM_RENDERER.NAMELESS_TASK' | translate}} + + +

+
+
+ + + + + + + + + + +
+
+
diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.scss b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.scss new file mode 100644 index 0000000000..155bd52de7 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.scss @@ -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; + } + } +} diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts index 4a970df4e3..5207ea6186 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts @@ -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 { + 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', () => { diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts index 0c57370bc4..23adc37a32 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts @@ -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(); @@ -96,19 +106,32 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, @Output() formContentClicked = new EventEmitter(); + /** Emitted when a display mode configuration is turned on. */ + @Output() + displayModeOn = new EventEmitter(); + + /** Emitted when a display mode configuration is turned off. */ + @Output() + displayModeOff = new EventEmitter(); + protected subscriptions: Subscription[] = []; nodeId: string; formCloudRepresentationJSON: any; protected onDestroy$ = new Subject(); + 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); + } } diff --git a/lib/process-services-cloud/src/lib/form/form-cloud.module.ts b/lib/process-services-cloud/src/lib/form/form-cloud.module.ts index 88c7e67525..a1a8d70c8a 100644 --- a/lib/process-services-cloud/src/lib/form/form-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/form/form-cloud.module.ts @@ -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, diff --git a/lib/process-services-cloud/src/lib/form/public-api.ts b/lib/process-services-cloud/src/lib/form/public-api.ts index deea686958..fb0391a50f 100644 --- a/lib/process-services-cloud/src/lib/form/public-api.ts +++ b/lib/process-services-cloud/src/lib/form/public-api.ts @@ -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'; diff --git a/lib/process-services-cloud/src/lib/form/services/display-mode.service.spec.ts b/lib/process-services-cloud/src/lib/form/services/display-mode.service.spec.ts new file mode 100644 index 0000000000..78560a0d86 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/services/display-mode.service.spec.ts @@ -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(); + }); +}); diff --git a/lib/process-services-cloud/src/lib/form/services/display-mode.service.ts b/lib/process-services-cloud/src/lib/form/services/display-mode.service.ts new file mode 100644 index 0000000000..d32fd64e00 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/services/display-mode.service.ts @@ -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(); + + static readonly displayMode$: Observable = 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); + } + } +} diff --git a/lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts b/lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts index 4c1246d984..bdbf9e42a5 100644 --- a/lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts +++ b/lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts @@ -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; +}; diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.html index 901dce6f4b..930bd5a761 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.html +++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.html @@ -1,5 +1,5 @@
- + (executeOutcome)="onFormExecuteOutcome($event)" + (displayModeOn)="onDisplayModeOn($event)" + (displayModeOff)="onDisplayModeOff($event)"> diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.spec.ts index 25ddb985a0..14fe35c024 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.spec.ts @@ -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); + }); }); diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.ts index 25a4c6eb85..91639db01d 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.ts @@ -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(); @@ -109,6 +117,17 @@ export class TaskFormCloudComponent implements OnInit, OnChanges, OnDestroy { @Output() onTaskLoaded = new EventEmitter(); /* eslint-disable-line */ + /** Emitted when a display mode configuration is turned on. */ + @Output() + displayModeOn = new EventEmitter(); + + /** Emitted when a display mode configuration is turned off. */ + @Output() + displayModeOff = new EventEmitter(); + + @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();