Merge pull request #12540 from ArtemDzhereleiko/AD/imp/multiple-input/radio

Radio button for multiple input widget
This commit is contained in:
Igor Kulikov 2025-01-31 18:14:17 +02:00 committed by GitHub
commit c5b744f6fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 208 additions and 112 deletions

View File

@ -138,6 +138,28 @@
{{ getErrorMessageText(key.settings, 'required') }} {{ getErrorMessageText(key.settings, 'required') }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<div *ngIf="key.settings.dataKeyValueType === 'radio'">
<mat-label>{{key.label}}</mat-label>
<mat-radio-group
[style]="{'display': 'grid', 'grid-template-columns': 'repeat(' + key.settings.radioColumns + ', 1fr)', 'column-gap': '5px', 'row-gap': '5px'}"
[labelPosition]="key.settings.radioLabelPosition"
(change)="inputChanged(source, key)"
formControlName="{{key.formId}}">
<mat-radio-button
[style]="radioButtonSelectedColor(key.settings.radioColor)"
[disabled]="key.settings.isEditable === 'readonly'"
*ngFor="let option of key.settings.selectOptions"
[value]="option.value">
<ng-container *ngIf="key.settings.icon || key.settings.customIconUrl" matIconPrefix>
<ng-container *ngTemplateOutlet="iconPrefix; context: {key: key}"></ng-container>
</ng-container>
<span class="label-wrapper">
{{ getCustomTranslationText(option.label ? option.label : option.value) }}
</span>
</mat-radio-button>
</mat-radio-group>
</div>
<mat-form-field *ngIf="key.settings.dataKeyValueType === 'color'" <mat-form-field *ngIf="key.settings.dataKeyValueType === 'color'"
class="color-input" [appearance]="key.settings.appearance" [subscriptSizing]="key.settings.subscriptSizing" class="color-input" [appearance]="key.settings.appearance" [subscriptSizing]="key.settings.subscriptSizing"
(click)="colorInput.openColorPickerPopup($event)"> (click)="colorInput.openColorPickerPopup($event)">

View File

@ -75,7 +75,7 @@
:host ::ng-deep { :host ::ng-deep {
.tb-multiple-input { .tb-multiple-input {
.mat-mdc-slide-toggle, .mat-mdc-checkbox { .mat-mdc-slide-toggle, .mat-mdc-checkbox, .mat-mdc-radio-button {
.mdc-form-field { .mdc-form-field {
width: 100%; width: 100%;
& > label { & > label {

View File

@ -54,7 +54,7 @@ type FieldAlignment = 'row' | 'column';
type MultipleInputWidgetDataKeyType = 'server' | 'shared' | 'timeseries'; type MultipleInputWidgetDataKeyType = 'server' | 'shared' | 'timeseries';
export type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' | export type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' |
'JSON' | 'booleanCheckbox' | 'booleanSwitch' | 'JSON' | 'booleanCheckbox' | 'booleanSwitch' |
'dateTime' | 'date' | 'time' | 'select' | 'color'; 'dateTime' | 'date' | 'time' | 'select' | 'radio' | 'color';
export type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly'; export type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly';
type ConvertGetValueFunction = (value: any, ctx: WidgetContext) => any; type ConvertGetValueFunction = (value: any, ctx: WidgetContext) => any;
@ -86,6 +86,9 @@ interface MultipleInputWidgetDataKeySettings {
dataKeyValueType: MultipleInputWidgetDataKeyValueType; dataKeyValueType: MultipleInputWidgetDataKeyValueType;
slideToggleLabelPosition?: 'after' | 'before'; slideToggleLabelPosition?: 'after' | 'before';
selectOptions: MultipleInputWidgetSelectOption[]; selectOptions: MultipleInputWidgetSelectOption[];
radioColor: string;
radioColumns: number;
radioLabelPosition?: 'after' | 'before';
required: boolean; required: boolean;
isEditable: MultipleInputWidgetDataKeyEditableType; isEditable: MultipleInputWidgetDataKeyEditableType;
disabledOnDataKey: string; disabledOnDataKey: string;
@ -300,7 +303,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
// For backward compatibility // For backward compatibility
if (dataKey.settings.dataKeyValueType === 'select') { if (dataKey.settings.dataKeyValueType === 'select' || dataKey.settings.dataKeyValueType === 'radio') {
dataKey.settings.selectOptions.forEach((option) => { dataKey.settings.selectOptions.forEach((option) => {
if (option.value.toLowerCase() === 'null') { if (option.value.toLowerCase() === 'null') {
option.value = null; option.value = null;
@ -444,6 +447,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
} }
break; break;
case 'select': case 'select':
case 'radio':
value = keyValue !== null ? keyValue.toString() : null; value = keyValue !== null ? keyValue.toString() : null;
break; break;
case 'JSON': case 'JSON':
@ -566,6 +570,12 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
return this.getTranslatedErrorText(errorMessage, defaultMessage, messageValues); return this.getTranslatedErrorText(errorMessage, defaultMessage, messageValues);
} }
public radioButtonSelectedColor(radioColor: string) {
if (isDefinedAndNotNull(radioColor)) {
return `--mdc-radio-selected-icon-color: ${radioColor}; --mdc-radio-selected-focus-icon-color: ${radioColor}; --mdc-radio-selected-hover-icon-color: ${radioColor}; --mdc-radio-selected-pressed-icon-color: ${radioColor}; --mat-radio-checked-ripple-color: ${radioColor};`
}
}
public getTranslatedErrorText(errorMessage: string, defaultMessage: string, messageValues?: object): string { public getTranslatedErrorText(errorMessage: string, defaultMessage: string, messageValues?: object): string {
let messageText; let messageText;
if (errorMessage && errorMessage.length) { if (errorMessage && errorMessage.length) {

View File

@ -21,6 +21,7 @@
</mat-slide-toggle> </mat-slide-toggle>
<fieldset [class.!hidden]="updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value" class="fields-group"> <fieldset [class.!hidden]="updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value" class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.general-settings</legend> <legend class="group-title" translate>widgets.input-widgets.general-settings</legend>
<div class="flex flex-col">
<section class="flex flex-col gt-xs:flex-row gt-xs:items-center gt-xs:justify-start gt-xs:gap-2"> <section class="flex flex-col gt-xs:flex-row gt-xs:items-center gt-xs:justify-start gt-xs:gap-2">
<mat-form-field class="mat-block flex-full xs:max-h-50% gt-xs:max-w-50%"> <mat-form-field class="mat-block flex-full xs:max-h-50% gt-xs:max-w-50%">
<mat-label translate>widgets.input-widgets.datakey-type</mat-label> <mat-label translate>widgets.input-widgets.datakey-type</mat-label>
@ -66,6 +67,9 @@
<mat-option [value]="'select'"> <mat-option [value]="'select'">
{{ 'widgets.input-widgets.datakey-value-type-select' | translate }} {{ 'widgets.input-widgets.datakey-value-type-select' | translate }}
</mat-option> </mat-option>
<mat-option [value]="'radio'">
{{ 'widgets.input-widgets.datakey-value-type-radio' | translate }}
</mat-option>
<mat-option [value]="'JSON'"> <mat-option [value]="'JSON'">
{{ 'widgets.input-widgets.datakey-value-type-json' | translate }} {{ 'widgets.input-widgets.datakey-value-type-json' | translate }}
</mat-option> </mat-option>
@ -98,7 +102,7 @@
<input matInput formControlName="disabledOnDataKey"> <input matInput formControlName="disabledOnDataKey">
</mat-form-field> </mat-form-field>
</section> </section>
<section *ngIf="!['booleanSwitch', 'booleanCheckbox'].includes(updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value)" <section *ngIf="!['booleanSwitch', 'booleanCheckbox', 'radio'].includes(updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value)"
class="flex flex-col gt-xs:flex-row gt-xs:items-center gt-xs:justify-start gt-xs:gap-2"> class="flex flex-col gt-xs:flex-row gt-xs:items-center gt-xs:justify-start gt-xs:gap-2">
<mat-form-field class="mat-block flex-full xs:max-h-50% gt-xs:max-w-50%"> <mat-form-field class="mat-block flex-full xs:max-h-50% gt-xs:max-w-50%">
<mat-label translate>widgets.input-widgets.field-appearance</mat-label> <mat-label translate>widgets.input-widgets.field-appearance</mat-label>
@ -123,6 +127,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</section> </section>
</div>
</fieldset> </fieldset>
<fieldset [class.!hidden]="updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value || <fieldset [class.!hidden]="updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value ||
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'booleanSwitch'" class="fields-group"> updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'booleanSwitch'" class="fields-group">
@ -140,8 +145,10 @@
</mat-form-field> </mat-form-field>
</fieldset> </fieldset>
<fieldset [class.!hidden]="updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value || <fieldset [class.!hidden]="updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value ||
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'select'" class="fields-group"> (updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'select' && updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'radio')" class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.select-options</legend> <legend class="group-title">
{{ (updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'select' ? 'widgets.input-widgets.select-options' : 'widgets.input-widgets.radio-options') | translate }}
</legend>
<div class="flex flex-col"> <div class="flex flex-col">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical" <div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="selectOptionDrop($event)"> (cdkDropListDropped)="selectOptionDrop($event)">
@ -155,18 +162,51 @@
</div> </div>
</div> </div>
<div *ngIf="!selectOptionsFormArray().controls.length"> <div *ngIf="!selectOptionsFormArray().controls.length">
<span translate <span class="tb-prompt flex items-center justify-center">
class="tb-prompt flex items-center justify-center">widgets.input-widgets.no-select-options</span> {{ (updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'select' ? 'widgets.input-widgets.no-select-options' : 'widgets.input-widgets.no-radio-options') | translate }}
</span>
</div> </div>
<div style="padding-top: 16px;"> <div style="padding-top: 16px;">
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
type="button" type="button"
(click)="addSelectOption()"> (click)="addSelectOption()">
<span translate>widgets.input-widgets.add-select-option</span> <span>
{{ (updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'select' ? 'widgets.input-widgets.add-select-option' : 'widgets.input-widgets.add-radio-option') | translate }}
</span>
</button> </button>
</div> </div>
</div> </div>
</fieldset> </fieldset>
<div class="tb-form-panel mb-3.5"
[class.!hidden]="updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value ||
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'radio'">
<div class="tb-form-panel-title" translate>widgets.input-widgets.radio-button-settings</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.input-widgets.color' | translate }}</div>
<tb-color-input asBoxInput
formControlName="radioColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.input-widgets.columns' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="radioColumns" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.input-widgets.radio-label-position' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="radioLabelPosition">
<mat-option [value]="'after'">
{{ 'widgets.input-widgets.radio-label-position-after' | translate }}
</mat-option>
<mat-option [value]="'before'">
{{ 'widgets.input-widgets.radio-label-position-before' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<fieldset [class.!hidden]="updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value || <fieldset [class.!hidden]="updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value ||
(updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'integer' && (updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'integer' &&
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'double')" class="fields-group"> updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'double')" class="fields-group">

View File

@ -60,6 +60,9 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings
slideToggleLabelPosition: 'after', slideToggleLabelPosition: 'after',
selectOptions: [], selectOptions: [],
radioColor: null,
radioColumns: 1,
radioLabelPosition: 'after',
step: 1, step: 1,
minValue: null, minValue: null,
maxValue: null, maxValue: null,
@ -104,10 +107,16 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings
slideToggleLabelPosition: [settings.slideToggleLabelPosition, []], slideToggleLabelPosition: [settings.slideToggleLabelPosition, []],
// Select options // Select/Radio options
selectOptions: this.prepareSelectOptionsFormArray(settings.selectOptions), selectOptions: this.prepareSelectOptionsFormArray(settings.selectOptions),
// Radio settings
radioColor: [settings.radioColor, []],
radioColumns: [settings.radioColumns, []],
radioLabelPosition: [settings.radioLabelPosition, []],
// Numeric field settings // Numeric field settings
step: [settings.step, [Validators.min(0)]], step: [settings.step, [Validators.min(0)]],
@ -183,6 +192,11 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings
this.updateMultipleAttributesKeySettingsForm.get('slideToggleLabelPosition').enable({emitEvent: false}); this.updateMultipleAttributesKeySettingsForm.get('slideToggleLabelPosition').enable({emitEvent: false});
} else if (dataKeyValueType === 'select') { } else if (dataKeyValueType === 'select') {
this.updateMultipleAttributesKeySettingsForm.get('selectOptions').enable({emitEvent: false}); this.updateMultipleAttributesKeySettingsForm.get('selectOptions').enable({emitEvent: false});
} else if (dataKeyValueType === 'radio') {
this.updateMultipleAttributesKeySettingsForm.get('selectOptions').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('radioColor').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('radioColumns').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('radioLabelPosition').enable({emitEvent: false});
} else if (dataKeyValueType === 'integer' || dataKeyValueType === 'double') { } else if (dataKeyValueType === 'integer' || dataKeyValueType === 'double') {
this.updateMultipleAttributesKeySettingsForm.get('step').enable({emitEvent: false}); this.updateMultipleAttributesKeySettingsForm.get('step').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('minValue').enable({emitEvent: false}); this.updateMultipleAttributesKeySettingsForm.get('minValue').enable({emitEvent: false});

View File

@ -7400,6 +7400,7 @@
"datakey-value-type-date": "Date", "datakey-value-type-date": "Date",
"datakey-value-type-time": "Time", "datakey-value-type-time": "Time",
"datakey-value-type-select": "Select", "datakey-value-type-select": "Select",
"datakey-value-type-radio": "Radio",
"datakey-value-type-color": "Color", "datakey-value-type-color": "Color",
"value-is-required": "Value is required", "value-is-required": "Value is required",
"ability-to-edit-attribute": "Ability to edit attribute", "ability-to-edit-attribute": "Ability to edit attribute",
@ -7440,7 +7441,16 @@
"set-value-function": "setValue function", "set-value-function": "setValue function",
"json-invalid": "JSON value has an invalid format", "json-invalid": "JSON value has an invalid format",
"title": "Title", "title": "Title",
"cancel-button-label": "'Cancel' button label" "cancel-button-label": "'Cancel' button label",
"radio-button-settings": "Radio button settings",
"color": "Color",
"columns": "Columns",
"radio-options": "Radio options",
"no-radio-options": "No radio options configured",
"add-radio-option": "Add radio option",
"radio-label-position": "Label position",
"radio-label-position-before": "Before",
"radio-label-position-after": "After"
}, },
"invalid-qr-code-text": "Invalid input text for QR code. Input should have a string type", "invalid-qr-code-text": "Invalid input text for QR code. Input should have a string type",
"qr-code": { "qr-code": {