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 {