From ab67ba3414f7aec44fe36a6717ece5f09899ddaf Mon Sep 17 00:00:00 2001 From: Wiktor Danielewski <63188869+wiktord2000@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:20:30 +0100 Subject: [PATCH] AAE-39155 Implement button widget on ADF (#11324) * AAE-39155 Initial implementation * AAE-39155 Improve button position --- .../widgets/button/button.widget.html | 11 ++ .../widgets/button/button.widget.scss | 14 +++ .../widgets/button/button.widget.spec.ts | 105 ++++++++++++++++++ .../widgets/button/button.widget.ts | 52 +++++++++ .../widgets/core/form-field-types.ts | 1 + .../src/lib/form/components/widgets/index.ts | 5 +- .../form/services/form-rendering.service.ts | 3 +- .../lib/services/form-fields.interfaces.ts | 3 +- 8 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 lib/core/src/lib/form/components/widgets/button/button.widget.html create mode 100644 lib/core/src/lib/form/components/widgets/button/button.widget.scss create mode 100644 lib/core/src/lib/form/components/widgets/button/button.widget.spec.ts create mode 100644 lib/core/src/lib/form/components/widgets/button/button.widget.ts diff --git a/lib/core/src/lib/form/components/widgets/button/button.widget.html b/lib/core/src/lib/form/components/widgets/button/button.widget.html new file mode 100644 index 0000000000..4cbbc59f25 --- /dev/null +++ b/lib/core/src/lib/form/components/widgets/button/button.widget.html @@ -0,0 +1,11 @@ + diff --git a/lib/core/src/lib/form/components/widgets/button/button.widget.scss b/lib/core/src/lib/form/components/widgets/button/button.widget.scss new file mode 100644 index 0000000000..fc2252757a --- /dev/null +++ b/lib/core/src/lib/form/components/widgets/button/button.widget.scss @@ -0,0 +1,14 @@ +.adf-button-widget { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + min-height: 96px; + + &__button { + margin-bottom: 32px; + word-break: break-word; + overflow: hidden; + } +} diff --git a/lib/core/src/lib/form/components/widgets/button/button.widget.spec.ts b/lib/core/src/lib/form/components/widgets/button/button.widget.spec.ts new file mode 100644 index 0000000000..971bd1ce35 --- /dev/null +++ b/lib/core/src/lib/form/components/widgets/button/button.widget.spec.ts @@ -0,0 +1,105 @@ +/*! + * @license + * Copyright © 2005-2025 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 { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ButtonWidgetComponent } from './button.widget'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatButtonHarness } from '@angular/material/button/testing'; +import { MatTooltipHarness } from '@angular/material/tooltip/testing'; + +describe('ButtonWidgetComponent', () => { + let component: ButtonWidgetComponent; + let fixture: ComponentFixture; + let loader: HarnessLoader; + + const buttonSelector = '.adf-button-widget__button'; + const mockField = { id: 'button1', name: 'Click me!', type: 'button', readOnly: false, className: 'custom-button-class', tooltip: '' }; + + const getButton = async () => loader.getHarness(MatButtonHarness.with({ selector: buttonSelector })); + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ButtonWidgetComponent] + }); + + fixture = TestBed.createComponent(ButtonWidgetComponent); + component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + + fixture.componentRef.setInput('field', mockField); + }); + + it('should display button with the given name', async () => { + const button = await getButton(); + const buttonText = await button.getText(); + + expect(buttonText).toBe('Click me!'); + }); + + it('should NOT disable button when readOnly is false', async () => { + const button = await getButton(); + + expect(await button.isDisabled()).toBe(false); + }); + + it('should disable button when readOnly is true', async () => { + fixture.componentRef.setInput('field', { ...mockField, readOnly: true }); + const button = await getButton(); + + expect(await button.isDisabled()).toBe(true); + }); + + it('should attach className to the widget host element', () => { + fixture.detectChanges(); + + const hostElement = fixture.nativeElement; + + expect(hostElement.classList).toContain('custom-button-class'); + }); + + it('should NOT show tooltip when tooltip text is not defined', async () => { + const buttonTooltip = await loader.getHarness(MatTooltipHarness.with({ selector: buttonSelector })); + + await buttonTooltip.show(); + + expect(await buttonTooltip.isOpen()).toBe(false); + }); + + it('should show tooltip when tooltip text is defined', async () => { + const expectedTooltip = 'This is a tooltip'; + + fixture.componentRef.setInput('field', { ...mockField, tooltip: expectedTooltip }); + + const buttonTooltip = await loader.getHarness(MatTooltipHarness.with({ selector: buttonSelector })); + + await buttonTooltip.show(); + + expect(await buttonTooltip.isOpen()).toBe(true); + expect(await buttonTooltip.getTooltipText()).toBe(expectedTooltip); + }); + + it('should call event method only once when widget is clicked', async () => { + const eventSpy = spyOn(component, 'event'); + + const button = await getButton(); + await button.click(); + + expect(eventSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/lib/core/src/lib/form/components/widgets/button/button.widget.ts b/lib/core/src/lib/form/components/widgets/button/button.widget.ts new file mode 100644 index 0000000000..2423cc73c1 --- /dev/null +++ b/lib/core/src/lib/form/components/widgets/button/button.widget.ts @@ -0,0 +1,52 @@ +/*! + * @license + * Copyright © 2005-2025 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. + */ + +/* eslint-disable @angular-eslint/component-selector */ + +import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; +import { WidgetComponent } from '../widget.component'; +import { FormService } from '../../../services/form.service'; +import { TranslatePipe } from '@ngx-translate/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +@Component({ + selector: 'button-widget', + templateUrl: './button.widget.html', + styleUrl: './button.widget.scss', + host: { + '[class]': 'hostClasses' + }, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TranslatePipe, MatButtonModule, MatTooltipModule] +}) +export class ButtonWidgetComponent extends WidgetComponent { + readonly tooltipShowDelay: number = 500; + + constructor(public formService: FormService) { + super(formService); + } + + get hostClasses(): string { + return `adf-button-widget ${this.field?.className || ''}`; + } + + onClick(event: Event): void { + this.event(event); + } +} diff --git a/lib/core/src/lib/form/components/widgets/core/form-field-types.ts b/lib/core/src/lib/form/components/widgets/core/form-field-types.ts index 8b5ae79305..5b5929eced 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field-types.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field-types.ts @@ -53,6 +53,7 @@ export class FormFieldTypes { static JSON: string = 'json'; static DATA_TABLE: string = 'data-table'; static DISPLAY_EXTERNAL_PROPERTY: string = 'display-external-property'; + static BUTTON: string = 'button'; static READONLY_TYPES: string[] = [FormFieldTypes.HYPERLINK, FormFieldTypes.DISPLAY_VALUE, FormFieldTypes.READONLY_TEXT, FormFieldTypes.GROUP]; diff --git a/lib/core/src/lib/form/components/widgets/index.ts b/lib/core/src/lib/form/components/widgets/index.ts index 78393c75c3..a45a3fa453 100644 --- a/lib/core/src/lib/form/components/widgets/index.ts +++ b/lib/core/src/lib/form/components/widgets/index.ts @@ -31,6 +31,7 @@ import { DateTimeWidgetComponent } from './date-time/date-time.widget'; import { JsonWidgetComponent } from './json/json.widget'; import { BaseViewerWidgetComponent } from './base-viewer/base-viewer.widget'; import { DecimalWidgetComponent } from './decimal/decimal.component'; +import { ButtonWidgetComponent } from './button/button.widget'; // core export * from './widget.component'; @@ -52,6 +53,7 @@ export * from './date-time/date-time.widget'; export * from './json/json.widget'; export * from './base-viewer/base-viewer.widget'; export * from './text/text-mask.component'; +export * from './button/button.widget'; // widgets with schema export * from './display-text'; @@ -72,7 +74,8 @@ export const WIDGET_DIRECTIVES = [ ErrorWidgetComponent, DateTimeWidgetComponent, JsonWidgetComponent, - BaseViewerWidgetComponent + BaseViewerWidgetComponent, + ButtonWidgetComponent ] as const; export const MASK_DIRECTIVE = [InputMaskDirective] as const; diff --git a/lib/core/src/lib/form/services/form-rendering.service.ts b/lib/core/src/lib/form/services/form-rendering.service.ts index 085c439cde..aa18ec7db7 100644 --- a/lib/core/src/lib/form/services/form-rendering.service.ts +++ b/lib/core/src/lib/form/services/form-rendering.service.ts @@ -44,6 +44,7 @@ export class FormRenderingService extends DynamicComponentMapper { [FormFieldTypes.JSON]: DynamicComponentResolver.fromType(widgets.JsonWidgetComponent), [FormFieldTypes.DISPLAY_VALUE]: DynamicComponentResolver.fromType(widgets.TextWidgetComponent), [FormFieldTypes.DATETIME]: DynamicComponentResolver.fromType(widgets.DateTimeWidgetComponent), - [FormFieldTypes.VIEWER]: DynamicComponentResolver.fromType(widgets.BaseViewerWidgetComponent) + [FormFieldTypes.VIEWER]: DynamicComponentResolver.fromType(widgets.BaseViewerWidgetComponent), + [FormFieldTypes.BUTTON]: DynamicComponentResolver.fromType(widgets.ButtonWidgetComponent) }; } 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 a490fb1dee..b37d09c3a0 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 @@ -258,7 +258,8 @@ export enum FormFieldType { uploadFolder = 'uploadFolder', displayValue = 'readonly', displayText = 'readonly-text', - fileViewer = 'file-viewer' + fileViewer = 'file-viewer', + button = 'button' } export interface FormCloudDisplayModeConfigurationOptions {