AAE-39155 Implement button widget on ADF (#11324)

* AAE-39155 Initial implementation

* AAE-39155 Improve button position
This commit is contained in:
Wiktor Danielewski
2025-11-04 12:20:30 +01:00
committed by GitHub
parent c4f276e228
commit ab67ba3414
8 changed files with 191 additions and 3 deletions
@@ -0,0 +1,11 @@
<button
mat-flat-button
class="adf-button-widget__button"
[color]="'primary'"
[matTooltip]="field?.tooltip"
[matTooltipShowDelay]="tooltipShowDelay"
[disabled]="field?.readOnly ?? false"
(click)="onClick($event)"
>
{{ field?.name | translate }}
</button>
@@ -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;
}
}
@@ -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<ButtonWidgetComponent>;
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);
});
});
@@ -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);
}
}
@@ -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];
@@ -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;
@@ -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)
};
}