From 96111f588a3f094da16f85c98e85f5eedcb6bd64 Mon Sep 17 00:00:00 2001 From: Silviu Popa Date: Tue, 14 May 2019 11:15:15 +0300 Subject: [PATCH] [ADF-4488] Form - Add dropdown-cloud widget (#4695) * [ADF-4488] Form - Add dropdown cloud widget * [ADF-4488] - add form style * [ADF-4488] - revert formFieldOption change * [ADF-4488] - PR changes * [ADF-4488] - move dropdown widget into cloud module * [ADF-4488] - PR changes and remove unused unit test * [ADF-4488] - PR changes * [ADF-4488] - PR changes * [ADF-4488] - add multilingual support * [ADF-4488] - PR changes --- .../dropdown-cloud/dropdown-cloud.widget.html | 20 ++ .../dropdown-cloud/dropdown-cloud.widget.scss | 25 +++ .../dropdown-cloud.widget.spec.ts | 191 ++++++++++++++++++ .../dropdown-cloud/dropdown-cloud.widget.ts | 92 +++++++++ .../src/lib/form/form-cloud.module.ts | 10 +- .../lib/form/services/form-cloud.service.ts | 22 +- .../components/task-form-cloud.component.ts | 2 + 7 files changed, 358 insertions(+), 4 deletions(-) create mode 100644 lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.html create mode 100644 lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.scss create mode 100644 lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.spec.ts create mode 100644 lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.ts diff --git a/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.html new file mode 100644 index 0000000000..fa2d9eb593 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.html @@ -0,0 +1,20 @@ +
+ + + + {{opt.name}} + + {{field.value}} + + + + +
diff --git a/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.scss b/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.scss new file mode 100644 index 0000000000..8e397e6e53 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.scss @@ -0,0 +1,25 @@ +.adf { + &-dropdown-widget { + width: 100%; + margin-top: 13px; + + .adf-select { + padding-top: 0 !important; + width: 100%; + } + + .mat-select-value-text { + font-size: 14px; + } + + &-select { + width: 100%; + } + + &-dropdown-required-message .adf-error-text-container { + margin-top: 1px !important; + } + + } + +} diff --git a/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.spec.ts new file mode 100644 index 0000000000..92203fa610 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.spec.ts @@ -0,0 +1,191 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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 { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { of } from 'rxjs'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { DropdownCloudWidgetComponent } from './dropdown-cloud.widget'; +import { FormService, WidgetVisibilityService, FormFieldOption, setupTestBed, FormFieldModel, FormModel, CoreModule } from '@alfresco/adf-core'; +import { FormCloudService } from '../../services/form-cloud.service'; + +describe('DropdownCloudWidgetComponent', () => { + + let formService: FormService; + let widget: DropdownCloudWidgetComponent; + let visibilityService: WidgetVisibilityService; + let formCloudService: FormCloudService; + let fixture: ComponentFixture; + let element: HTMLElement; + + function openSelect() { + const dropdown = fixture.debugElement.query(By.css('[class="mat-select-trigger"]')); + dropdown.triggerEventHandler('click', null); + fixture.detectChanges(); + } + + const fakeOptionList: FormFieldOption[] = [ + { id: 'opt_1', name: 'option_1' }, + { id: 'opt_2', name: 'option_2' }, + { id: 'opt_3', name: 'option_3' }]; + + setupTestBed({ + imports: [ + NoopAnimationsModule, + CoreModule.forRoot() + ], + declarations: [DropdownCloudWidgetComponent], + providers: [FormCloudService] + }); + + beforeEach(async(() => { + fixture = TestBed.createComponent(DropdownCloudWidgetComponent); + widget = fixture.componentInstance; + element = fixture.nativeElement; + formService = TestBed.get(FormService); + visibilityService = TestBed.get(WidgetVisibilityService); + formCloudService = TestBed.get(FormCloudService); + + widget.field = new FormFieldModel(new FormModel()); + })); + + it('should require field with restUrl', async(() => { + spyOn(formService, 'getRestFieldValues').and.stub(); + + widget.field = null; + widget.ngOnInit(); + expect(formService.getRestFieldValues).not.toHaveBeenCalled(); + + widget.field = new FormFieldModel(null, { restUrl: null }); + widget.ngOnInit(); + expect(formService.getRestFieldValues).not.toHaveBeenCalled(); + })); + + describe('when template is ready', () => { + + describe('and dropdown is populated', () => { + + beforeEach(async(() => { + spyOn(visibilityService, 'refreshVisibility').and.stub(); + spyOn(formService, 'getRestFieldValues').and.callFake(() => { + return of(fakeOptionList); + }); + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'dropdown-id', + name: 'date-name', + type: 'dropdown', + readOnly: 'false', + restUrl: 'fake-rest-url' + }); + widget.field.emptyOption = { id: 'empty', name: 'Choose one...' }; + widget.field.isVisible = true; + fixture.detectChanges(); + })); + + it('should show visible dropdown widget', async(() => { + expect(element.querySelector('#dropdown-id')).toBeDefined(); + expect(element.querySelector('#dropdown-id')).not.toBeNull(); + + openSelect(); + + const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]')); + const optTwo = fixture.debugElement.queryAll(By.css('[id="mat-option-2"]')); + const optThree = fixture.debugElement.queryAll(By.css('[id="mat-option-3"]')); + + expect(optOne).not.toBeNull(); + expect(optTwo).not.toBeNull(); + expect(optThree).not.toBeNull(); + })); + + it('should select the default value when an option is chosen as default', async(() => { + widget.field.value = 'option_2'; + widget.ngOnInit(); + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + const dropDownElement: any = element.querySelector('#dropdown-id'); + expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('option_2'); + expect(dropDownElement.attributes['ng-reflect-model'].textContent).toBe('option_2'); + }); + })); + + it('should select the empty value when no default is chosen', async(() => { + widget.field.value = 'empty'; + widget.ngOnInit(); + fixture.detectChanges(); + + openSelect(); + + fixture.detectChanges(); + + fixture.whenStable() + .then(() => { + const dropDownElement: any = element.querySelector('#dropdown-id'); + expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('empty'); + }); + })); + + it('should load data from restUrl and populate options', async(() => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'dropdown-id', + name: 'date-name', + type: 'dropdown-cloud', + readOnly: 'true', + restUrl: 'fake-rest-url', + optionType: 'rest', + restIdProperty: 'name' + }); + const jsonDataSpy = spyOn(formCloudService, 'getDropDownJsonData').and.returnValue(of(fakeOptionList)); + const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]')); + widget.ngOnInit(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(jsonDataSpy).toHaveBeenCalled(); + expect(optOne).not.toBeNull(); + }); + })); + + it('shoud map properties if restResponsePath is set', async(() => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'dropdown-id', + name: 'date-name', + type: 'dropdown-cloud', + readOnly: 'true', + restUrl: 'fake-rest-url', + optionType: 'rest', + restResponsePath: 'path', + restIdProperty: 'name' + }); + + spyOn(formCloudService, 'getDropDownJsonData').and.returnValue(of([{ + id: 1, + path: { + name: 'test1' + } + }])); + spyOn(widget, 'mapJsonData').and.returnValue([]); + + widget.ngOnInit(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(widget.mapJsonData).toHaveBeenCalled(); + }); + })); + + }); + }); +}); diff --git a/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.ts new file mode 100644 index 0000000000..7058a13ee2 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/dropdown-cloud/dropdown-cloud.widget.ts @@ -0,0 +1,92 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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 { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { baseHost, WidgetComponent, FormService, LogService, FormFieldOption } from '@alfresco/adf-core'; +import { FormCloudService } from '../../services/form-cloud.service'; + + /* tslint:disable:component-selector */ + +@Component({ + selector: 'dropdown-cloud-widget', + templateUrl: './dropdown-cloud.widget.html', + styleUrls: ['./dropdown-cloud.widget.scss'], + host: baseHost, + encapsulation: ViewEncapsulation.None +}) +export class DropdownCloudWidgetComponent extends WidgetComponent implements OnInit { + + constructor(public formService: FormService, + private formCloudService: FormCloudService, + private logService: LogService) { + super(formService); + } + + ngOnInit() { + if (this.field && this.field.restUrl) { + this.getValuesFromRestApi(); + } + } + + getValuesFromRestApi() { + if (this.isValidRestType()) { + this.formCloudService.getDropDownJsonData(this.field.restUrl).subscribe( (result: FormFieldOption[]) => { + if (this.field.restResponsePath) { + this.field.options = this.mapJsonData(result); + } else { + this.field.options = result; + } + }, + (err) => this.handleError(err)); + } + } + + mapJsonData(data: any[]): FormFieldOption[] { + const path = this.field.restResponsePath; + const idProperty = this.field.restIdProperty; + + return data.map( (value: any) => { + return { + name: value[path][idProperty], + id: value.id + }; + }); + } + + getOptionValue(option: FormFieldOption, fieldValue: string): string { + let optionValue: string = ''; + if (option.id === 'empty' || option.name !== fieldValue) { + optionValue = option.id; + } else { + optionValue = option.name; + } + return optionValue; + } + + isValidRestType(): boolean { + return this.field.optionType === 'rest' && !!this.field.restUrl; + } + + handleError(error: any) { + this.logService.error(error); + } + + isReadOnlyType(): boolean { + return this.field.type === 'readonly' ? true : false; + } + + } 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 19c21a898c..7d843b6485 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 @@ -26,6 +26,7 @@ import { FormCloudComponent } from './components/form-cloud.component'; import { FormDefinitionSelectorCloudComponent } from './components/form-definition-selector-cloud.component'; import { FormDefinitionSelectorCloudService } from './services/form-definition-selector-cloud.service'; import { FormCustomOutcomesComponent } from './components/form-cloud-custom-outcomes.component'; +import { DropdownCloudWidgetComponent } from './components/dropdown-cloud/dropdown-cloud.widget'; @NgModule({ imports: [ @@ -39,10 +40,13 @@ import { FormCustomOutcomesComponent } from './components/form-cloud-custom-outc FormBaseModule, CoreModule ], - declarations: [FormCloudComponent, UploadCloudWidgetComponent, FormDefinitionSelectorCloudComponent, FormCustomOutcomesComponent], - providers: [FormDefinitionSelectorCloudService], + declarations: [FormCloudComponent, UploadCloudWidgetComponent, FormDefinitionSelectorCloudComponent, FormCustomOutcomesComponent, DropdownCloudWidgetComponent], + providers: [ + FormDefinitionSelectorCloudService + ], entryComponents: [ - UploadCloudWidgetComponent + UploadCloudWidgetComponent, + DropdownCloudWidgetComponent ], exports: [ FormCloudComponent, UploadCloudWidgetComponent, FormDefinitionSelectorCloudComponent, FormCustomOutcomesComponent diff --git a/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts b/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts index c6e2c04466..6172757f79 100644 --- a/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts @@ -16,7 +16,7 @@ */ import { Injectable } from '@angular/core'; -import { AlfrescoApiService, LogService, FormValues, AppConfigService, FormOutcomeModel } from '@alfresco/adf-core'; +import { AlfrescoApiService, LogService, FormValues, AppConfigService, FormOutcomeModel, FormFieldOption } from '@alfresco/adf-core'; import { throwError, Observable, from } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; import { TaskDetailsCloudModel } from '../../task/start-task/models/task-details-cloud.model'; @@ -224,6 +224,26 @@ export class FormCloudService extends BaseCloudService { ); } + /** + * Parses JSON data to create a corresponding form. + * @param url String data to make the request + * @returns Array of FormFieldOption object + */ + getDropDownJsonData(url: string): Observable { + return from(this.apiService.getInstance() + .oauth2Auth.callCustomApi(url, 'GET', + null, null, null, + null, null, + this.contentTypes, this.accepts, + this.returnType, null, null) + ).pipe( + map((res: any) => { + return res; + }), + catchError((err) => this.handleError(err)) + ); + } + /** * Parses JSON data to create a corresponding form. * @param json JSON data to create the form 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 5ecac0805d..fd5619cc2e 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 @@ -24,6 +24,7 @@ import { TaskDetailsCloudModel } from '../../start-task/models/task-details-clou import { TaskCloudService } from '../../services/task-cloud.service'; import { FormRenderingService } from '@alfresco/adf-core'; import { UploadCloudWidgetComponent } from '../../../form/components/upload-cloud.widget'; +import { DropdownCloudWidgetComponent } from '../../../form/components/dropdown-cloud/dropdown-cloud.widget'; @Component({ selector: 'adf-cloud-task-form', @@ -94,6 +95,7 @@ export class TaskFormCloudComponent implements OnChanges { private taskCloudService: TaskCloudService, private formRenderingService: FormRenderingService) { this.formRenderingService.setComponentTypeResolver('upload', () => UploadCloudWidgetComponent, true); + this.formRenderingService.setComponentTypeResolver('dropdown', () => DropdownCloudWidgetComponent, true); } ngOnChanges(changes: SimpleChanges) {