UI: Ability to import form properties from JSON content. Update help resources according to new widget settings forms.
This commit is contained in:
parent
39abbacb20
commit
f6071177ae
@ -37,7 +37,12 @@ import {
|
|||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
import { CdkDragDrop } from '@angular/cdk/drag-drop';
|
import { CdkDragDrop } from '@angular/cdk/drag-drop';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { FormProperty, FormPropertyType, propertyValid } from '@shared/models/dynamic-form.models';
|
import {
|
||||||
|
cleanupFormProperties,
|
||||||
|
FormProperty,
|
||||||
|
FormPropertyType,
|
||||||
|
propertyValid
|
||||||
|
} from '@shared/models/dynamic-form.models';
|
||||||
import {
|
import {
|
||||||
DynamicFormPropertyRowComponent
|
DynamicFormPropertyRowComponent
|
||||||
} from '@home/components/widget/lib/settings/common/dynamic-form/dynamic-form-property-row.component';
|
} from '@home/components/widget/lib/settings/common/dynamic-form/dynamic-form-property-row.component';
|
||||||
@ -134,7 +139,7 @@ export class DynamicFormPropertiesComponent implements ControlValueAccessor, OnI
|
|||||||
controls[i].patchValue(p, {emitEvent: false});
|
controls[i].patchValue(p, {emitEvent: false});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.propagateChange(properties);
|
this.propagateChange(cleanupFormProperties(properties));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,8 +54,24 @@ const widgetEditorCompletions = (settingsCompletions?: TbEditorCompletions): TbE
|
|||||||
description: 'Called when widget element is destroyed. Should be used to cleanup all resources if necessary.',
|
description: 'Called when widget element is destroyed. Should be used to cleanup all resources if necessary.',
|
||||||
meta: 'function'
|
meta: 'function'
|
||||||
},
|
},
|
||||||
|
getSettingsForm: {
|
||||||
|
description: 'Optional function returning widget settings form array as alternative to <b>Settings form</b> tab of settings section.',
|
||||||
|
meta: 'function',
|
||||||
|
return: {
|
||||||
|
description: 'An array of widget settings form properties',
|
||||||
|
type: 'Array<FormProperty>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDataKeySettingsForm: {
|
||||||
|
description: 'Optional function returning particular data key settings form array as alternative to <b>Data key settings form</b> tab of settings section.',
|
||||||
|
meta: 'function',
|
||||||
|
return: {
|
||||||
|
description: 'An array of data key settings form properties',
|
||||||
|
type: 'Array<FormProperty>'
|
||||||
|
}
|
||||||
|
},
|
||||||
getSettingsSchema: {
|
getSettingsSchema: {
|
||||||
description: 'Optional function returning widget settings schema json as alternative to <b>Settings tab</b> of <a href="https://thingsboard.io/docs/user-guide/contribution/widgets-development/#settings-schema-section" target="_blank">Settings schema section</a>.',
|
description: '<b>Deprecated</b>. Use getSettingsForm() function.',
|
||||||
meta: 'function',
|
meta: 'function',
|
||||||
return: {
|
return: {
|
||||||
description: 'An widget settings schema json',
|
description: 'An widget settings schema json',
|
||||||
@ -63,7 +79,7 @@ const widgetEditorCompletions = (settingsCompletions?: TbEditorCompletions): TbE
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDataKeySettingsSchema: {
|
getDataKeySettingsSchema: {
|
||||||
description: 'Optional function returning particular data key settings schema json as alternative to <b>Data key settings schema</b> of <a href="https://thingsboard.io/docs/user-guide/contribution/widgets-development/#settings-schema-section" target="_blank">Settings schema section</a>.',
|
description: '<b>Deprecated</b>. Use getDataKeySettingsForm() function.',
|
||||||
meta: 'function',
|
meta: 'function',
|
||||||
return: {
|
return: {
|
||||||
description: 'A particular data key settings schema json',
|
description: 'A particular data key settings schema json',
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
<form [formGroup]="importFormGroup" (ngSubmit)="importFromJson()">
|
<form [formGroup]="importFormGroup" (ngSubmit)="importFromJson()" style="width: 500px;">
|
||||||
<mat-toolbar color="primary">
|
<mat-toolbar color="primary">
|
||||||
<h2 translate>{{ importTitle }}</h2>
|
<h2 translate>{{ importTitle }}</h2>
|
||||||
<span class="flex-1"></span>
|
<span class="flex-1"></span>
|
||||||
@ -30,15 +30,28 @@
|
|||||||
<div mat-dialog-content>
|
<div mat-dialog-content>
|
||||||
<fieldset [disabled]="isLoading$ | async">
|
<fieldset [disabled]="isLoading$ | async">
|
||||||
<div class="flex flex-1 flex-col">
|
<div class="flex flex-1 flex-col">
|
||||||
<tb-file-input
|
<tb-toggle-select *ngIf="enableImportFromContent"
|
||||||
|
class="flex-1"
|
||||||
|
formControlName="importType">
|
||||||
|
<tb-toggle-option value="file">{{ importFileLabel | translate }}</tb-toggle-option>
|
||||||
|
<tb-toggle-option value="content">{{ importContentLabel | translate }}</tb-toggle-option>
|
||||||
|
</tb-toggle-select>
|
||||||
|
<tb-file-input *ngIf="importFormGroup.get('importType').value === 'file'"
|
||||||
[contentConvertFunction]="loadDataFromJsonContent"
|
[contentConvertFunction]="loadDataFromJsonContent"
|
||||||
formControlName="jsonContent"
|
[existingFileName]="currentFileName"
|
||||||
|
(fileNameChanged)="currentFileName = $event"
|
||||||
|
formControlName="fileContent"
|
||||||
required
|
required
|
||||||
label="{{importFileLabel | translate}}"
|
label="{{importFileLabel | translate}}"
|
||||||
dropLabel="{{ 'import.drop-json-file-or' | translate }}"
|
dropLabel="{{ 'import.drop-json-file-or' | translate }}"
|
||||||
accept=".json,application/json"
|
accept=".json,application/json"
|
||||||
allowedExtensions="json">
|
allowedExtensions="json">
|
||||||
</tb-file-input>
|
</tb-file-input>
|
||||||
|
<tb-json-object-edit *ngIf="importFormGroup.get('importType').value === 'content'"
|
||||||
|
formControlName="jsonContent"
|
||||||
|
jsonRequired
|
||||||
|
label="{{ importContentLabel | translate }}">
|
||||||
|
</tb-json-object-edit>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
|
import { Component, DestroyRef, Inject, OnInit, SkipSelf } from '@angular/core';
|
||||||
import { ErrorStateMatcher } from '@angular/material/core';
|
import { ErrorStateMatcher } from '@angular/material/core';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
@ -23,10 +23,14 @@ import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDire
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { DialogComponent } from '@app/shared/components/dialog.component';
|
import { DialogComponent } from '@app/shared/components/dialog.component';
|
||||||
import { ActionNotificationShow } from '@core/notification/notification.actions';
|
import { ActionNotificationShow } from '@core/notification/notification.actions';
|
||||||
|
import { isDefinedAndNotNull } from '@core/utils';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
export interface ImportDialogData {
|
export interface ImportDialogData {
|
||||||
importTitle: string;
|
importTitle: string;
|
||||||
importFileLabel: string;
|
importFileLabel: string;
|
||||||
|
enableImportFromContent?: boolean;
|
||||||
|
importContentLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -40,9 +44,13 @@ export class ImportDialogComponent extends DialogComponent<ImportDialogComponent
|
|||||||
|
|
||||||
importTitle: string;
|
importTitle: string;
|
||||||
importFileLabel: string;
|
importFileLabel: string;
|
||||||
|
enableImportFromContent: boolean;
|
||||||
|
importContentLabel: string;
|
||||||
|
|
||||||
importFormGroup: UntypedFormGroup;
|
importFormGroup: UntypedFormGroup;
|
||||||
|
|
||||||
|
currentFileName: string;
|
||||||
|
|
||||||
submitted = false;
|
submitted = false;
|
||||||
|
|
||||||
constructor(protected store: Store<AppState>,
|
constructor(protected store: Store<AppState>,
|
||||||
@ -50,17 +58,26 @@ export class ImportDialogComponent extends DialogComponent<ImportDialogComponent
|
|||||||
@Inject(MAT_DIALOG_DATA) public data: ImportDialogData,
|
@Inject(MAT_DIALOG_DATA) public data: ImportDialogData,
|
||||||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
|
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
|
||||||
public dialogRef: MatDialogRef<ImportDialogComponent>,
|
public dialogRef: MatDialogRef<ImportDialogComponent>,
|
||||||
|
private destroyRef: DestroyRef,
|
||||||
private fb: UntypedFormBuilder) {
|
private fb: UntypedFormBuilder) {
|
||||||
super(store, router, dialogRef);
|
super(store, router, dialogRef);
|
||||||
this.importTitle = data.importTitle;
|
this.importTitle = data.importTitle;
|
||||||
this.importFileLabel = data.importFileLabel;
|
this.importFileLabel = data.importFileLabel;
|
||||||
|
this.enableImportFromContent = isDefinedAndNotNull(data.enableImportFromContent) ? data.enableImportFromContent : false;
|
||||||
this.importFormGroup = this.fb.group({
|
this.importContentLabel = data.importContentLabel;
|
||||||
jsonContent: [null, [Validators.required]]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.importFormGroup = this.fb.group({
|
||||||
|
importType: ['file'],
|
||||||
|
fileContent: [null, [Validators.required]],
|
||||||
|
jsonContent: [null, [Validators.required]]
|
||||||
|
});
|
||||||
|
this.importFormGroup.get('importType').valueChanges.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
).subscribe(() => {
|
||||||
|
this.importTypeChanged();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||||
@ -85,7 +102,19 @@ export class ImportDialogComponent extends DialogComponent<ImportDialogComponent
|
|||||||
|
|
||||||
importFromJson(): void {
|
importFromJson(): void {
|
||||||
this.submitted = true;
|
this.submitted = true;
|
||||||
const importData = this.importFormGroup.get('jsonContent').value;
|
const importType: 'file' | 'content' = this.importFormGroup.get('importType').value;
|
||||||
|
const importData = this.importFormGroup.get(importType === 'file' ? 'fileContent' : 'jsonContent').value;
|
||||||
this.dialogRef.close(importData);
|
this.dialogRef.close(importData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private importTypeChanged() {
|
||||||
|
const importType: 'file' | 'content' = this.importFormGroup.get('importType').value;
|
||||||
|
if (importType === 'file') {
|
||||||
|
this.importFormGroup.get('fileContent').enable({emitEvent: false});
|
||||||
|
this.importFormGroup.get('jsonContent').disable({emitEvent: false});
|
||||||
|
} else {
|
||||||
|
this.importFormGroup.get('fileContent').disable({emitEvent: false});
|
||||||
|
this.importFormGroup.get('jsonContent').enable({emitEvent: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,7 +125,8 @@ export class ImportExportService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public importFormProperties(): Observable<FormProperty[]> {
|
public importFormProperties(): Observable<FormProperty[]> {
|
||||||
return this.openImportDialog('dynamic-form.import-form', 'dynamic-form.form-json-file').pipe(
|
return this.openImportDialog('dynamic-form.import-form',
|
||||||
|
'dynamic-form.json-file', true, 'dynamic-form.json-content').pipe(
|
||||||
map((properties: FormProperty[]) => {
|
map((properties: FormProperty[]) => {
|
||||||
if (!this.validateImportedFormProperties(properties)) {
|
if (!this.validateImportedFormProperties(properties)) {
|
||||||
this.store.dispatch(new ActionNotificationShow(
|
this.store.dispatch(new ActionNotificationShow(
|
||||||
@ -1134,14 +1135,17 @@ export class ImportExportService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private openImportDialog(importTitle: string, importFileLabel: string): Observable<any> {
|
private openImportDialog(importTitle: string, importFileLabel: string,
|
||||||
|
enableImportFromContent = false, importContentLabel?: string): Observable<any> {
|
||||||
return this.dialog.open<ImportDialogComponent, ImportDialogData,
|
return this.dialog.open<ImportDialogComponent, ImportDialogData,
|
||||||
any>(ImportDialogComponent, {
|
any>(ImportDialogComponent, {
|
||||||
disableClose: true,
|
disableClose: true,
|
||||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||||
data: {
|
data: {
|
||||||
importTitle,
|
importTitle,
|
||||||
importFileLabel
|
importFileLabel,
|
||||||
|
enableImportFromContent,
|
||||||
|
importContentLabel
|
||||||
}
|
}
|
||||||
}).afterClosed().pipe(
|
}).afterClosed().pipe(
|
||||||
map((importedData) => {
|
map((importedData) => {
|
||||||
|
|||||||
@ -325,7 +325,7 @@ export const widgetContextCompletionsWithSettings = (settingsCompletions?: TbEdi
|
|||||||
type: 'object'
|
type: 'object'
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
description: 'Widget settings containing widget specific properties according to the defined <a href="https://thingsboard.io/docs/user-guide/contribution/widgets-development/#settings-schema-section" target="_blank">settings json schema</a>',
|
description: 'Widget settings containing widget specific properties according to the defined settings form.',
|
||||||
meta: 'property',
|
meta: 'property',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
children: settingsCompletions
|
children: settingsCompletions
|
||||||
@ -363,7 +363,7 @@ export const widgetContextCompletionsWithSettings = (settingsCompletions?: TbEdi
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
description: 'Widget settings containing widget specific properties according to the defined <a href="https://thingsboard.io/docs/user-guide/contribution/widgets-development/#settings-schema-section" target="_blank">settings json schema</a>',
|
description: 'Widget settings containing widget specific properties according to the defined settings form.',
|
||||||
meta: 'property',
|
meta: 'property',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
children: settingsCompletions
|
children: settingsCompletions
|
||||||
|
|||||||
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
|
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
|
||||||
import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models';
|
import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models';
|
||||||
import { deepClone, isDefinedAndNotNull, isString } from '@core/utils';
|
import { deepClone, isDefinedAndNotNull, isEmptyStr, isString, isUndefinedOrNull } from '@core/utils';
|
||||||
import { JsonSchema, JsonSettingsSchema, JsonFormData, KeyLabelItem } from '@shared/legacy/json-form.models';
|
import { JsonFormData, JsonSchema, JsonSettingsSchema, KeyLabelItem } from '@shared/legacy/json-form.models';
|
||||||
import JsonFormUtils from '@shared/legacy/json-form-utils';
|
import JsonFormUtils from '@shared/legacy/json-form-utils';
|
||||||
import { constantColor, Font } from '@shared/models/widget-settings.models';
|
import { constantColor, Font } from '@shared/models/widget-settings.models';
|
||||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
@ -165,6 +165,63 @@ export interface FormHtmlSection extends FormPropertyBase {
|
|||||||
export type FormProperty = FormPropertyBase & FormTextareaProperty & FormNumberProperty & FormSelectProperty & FormRadiosProperty
|
export type FormProperty = FormPropertyBase & FormTextareaProperty & FormNumberProperty & FormSelectProperty & FormRadiosProperty
|
||||||
& FormDateTimeProperty & FormJavascriptProperty & FormMarkdownProperty & FormFieldSetProperty & FormArrayProperty & FormHtmlSection;
|
& FormDateTimeProperty & FormJavascriptProperty & FormMarkdownProperty & FormFieldSetProperty & FormArrayProperty & FormHtmlSection;
|
||||||
|
|
||||||
|
export const cleanupFormProperties = (properties: FormProperty[]): FormProperty[] => {
|
||||||
|
for (const property of properties) {
|
||||||
|
cleanupFormProperty(property);
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cleanupFormProperty = (property: FormProperty): FormProperty => {
|
||||||
|
if (property.type !== FormPropertyType.number) {
|
||||||
|
delete property.min;
|
||||||
|
delete property.max;
|
||||||
|
delete property.step;
|
||||||
|
}
|
||||||
|
if (property.type !== FormPropertyType.textarea) {
|
||||||
|
delete property.rows;
|
||||||
|
}
|
||||||
|
if (property.type !== FormPropertyType.fieldset) {
|
||||||
|
delete property.properties;
|
||||||
|
} else if (property.properties?.length) {
|
||||||
|
property.properties = cleanupFormProperties(property.properties);
|
||||||
|
}
|
||||||
|
if (property.type !== FormPropertyType.array) {
|
||||||
|
delete property.arrayItemName;
|
||||||
|
delete property.arrayItemType;
|
||||||
|
}
|
||||||
|
if (property.type !== FormPropertyType.select) {
|
||||||
|
delete property.multiple;
|
||||||
|
delete property.allowEmptyOption;
|
||||||
|
delete property.minItems;
|
||||||
|
delete property.maxItems;
|
||||||
|
}
|
||||||
|
if (property.type !== FormPropertyType.radios) {
|
||||||
|
delete property.direction;
|
||||||
|
}
|
||||||
|
if (![FormPropertyType.select, FormPropertyType.radios].includes(property.type)) {
|
||||||
|
delete property.items;
|
||||||
|
}
|
||||||
|
if (property.type !== FormPropertyType.datetime) {
|
||||||
|
delete property.allowClear;
|
||||||
|
delete property.dateTimeType;
|
||||||
|
}
|
||||||
|
if (![FormPropertyType.javascript, FormPropertyType.markdown].includes(property.type)) {
|
||||||
|
delete property.helpId;
|
||||||
|
}
|
||||||
|
if (property.type !== FormPropertyType.htmlSection) {
|
||||||
|
delete property.htmlClassList;
|
||||||
|
delete property.htmlContent;
|
||||||
|
}
|
||||||
|
for (const key of Object.keys(property)) {
|
||||||
|
const val = property[key];
|
||||||
|
if (isUndefinedOrNull(val) || isEmptyStr(val)) {
|
||||||
|
delete property[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
export enum FormPropertyContainerType {
|
export enum FormPropertyContainerType {
|
||||||
field = 'field',
|
field = 'field',
|
||||||
row = 'row',
|
row = 'row',
|
||||||
|
|||||||
@ -40,32 +40,25 @@ The **Widget Editor** will be opened, pre-populated with the content of the defa
|
|||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Put the following JSON content inside the "Settings schema" tab of **Settings schema section**:
|
- Import the following JSON content inside the "Settings form" tab by clicking on 'Import form from JSON' button:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
[
|
||||||
"schema": {
|
{
|
||||||
"type": "object",
|
"id": "alarmSeverityColorFunction",
|
||||||
"title": "AlarmTableSettings",
|
"name": "Alarm severity color function: f(severity)",
|
||||||
"properties": {
|
"type": "javascript",
|
||||||
"alarmSeverityColorFunction": {
|
"default": "if (severity == 'CRITICAL') {\n return 'red';\n} else if (severity == 'MAJOR') {\n return 'orange';\n} else return 'green';",
|
||||||
"title": "Alarm severity color function: f(severity)",
|
"required": false
|
||||||
"type": "string",
|
}
|
||||||
"default": "if(severity == 'CRITICAL') {return 'red';} else if (severity == 'MAJOR') {return 'orange';} else return 'green'; "
|
]
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": []
|
|
||||||
},
|
|
||||||
"form": [
|
|
||||||
{
|
|
||||||
"key": "alarmSeverityColorFunction",
|
|
||||||
"type": "javascript"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Clear all 'form selector' fields in the "Widget settings" tab.
|
||||||
|
|
||||||
|
- Turn off 'Has basic mode' switch in the "Widget settings" tab.
|
||||||
|
|
||||||
- Put the following JavaScript code inside the "JavaScript" section:
|
- Put the following JavaScript code inside the "JavaScript" section:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|||||||
@ -40,35 +40,28 @@ The **Widget Editor** will open, pre-populated with default **Control** template
|
|||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Put the following JSON content inside the "Settings schema" tab of **Settings schema section**:
|
- Import the following JSON content inside the "Settings form" tab by clicking on 'Import form from JSON' button:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
[
|
||||||
"schema": {
|
{
|
||||||
"type": "object",
|
"id": "oneWayElseTwoWay",
|
||||||
"title": "Settings",
|
"name": "Is One Way Command",
|
||||||
"properties": {
|
"type": "switch",
|
||||||
"oneWayElseTwoWay": {
|
"default": true
|
||||||
"title": "Is One Way Command",
|
},
|
||||||
"type": "boolean",
|
{
|
||||||
"default": true
|
"id": "requestTimeout",
|
||||||
},
|
"name": "RPC request timeout",
|
||||||
"requestTimeout": {
|
"type": "number",
|
||||||
"title": "RPC request timeout",
|
"default": 500
|
||||||
"type": "number",
|
}
|
||||||
"default": 500
|
]
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": []
|
|
||||||
},
|
|
||||||
"form": [
|
|
||||||
"oneWayElseTwoWay",
|
|
||||||
"requestTimeout"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Clear value of 'Settings form selector' in the "Widget settings" tab.
|
||||||
|
|
||||||
- Put the following JavaScript code inside the "JavaScript" section:
|
- Put the following JavaScript code inside the "JavaScript" section:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|||||||
@ -17,28 +17,23 @@ The **Widget Editor** will be opened pre-populated with the content of default *
|
|||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Put the following JSON content inside the "Settings schema" tab of **Settings schema section**:
|
- Import the following JSON content inside the "Settings form" tab by clicking on 'Import form from JSON' button:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
[
|
||||||
"schema": {
|
{
|
||||||
"type": "object",
|
"id": "alertContent",
|
||||||
"title": "Settings",
|
"name": "Alert content",
|
||||||
"properties": {
|
"type": "text",
|
||||||
"alertContent": {
|
"default": "Content derived from alertContent property of widget settings.",
|
||||||
"title": "Alert content",
|
"fieldClass": "flex"
|
||||||
"type": "string",
|
}
|
||||||
"default": "Content derived from alertContent property of widget settings."
|
]
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"form": [
|
|
||||||
"alertContent"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Clear value of 'Settings form selector' in the "Widget settings" tab.
|
||||||
|
|
||||||
- Put the following JavaScript code inside the "JavaScript" section:
|
- Put the following JavaScript code inside the "JavaScript" section:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|||||||
@ -10,18 +10,20 @@ Each widget function should be defined as a property of the **self** variable.
|
|||||||
|
|
||||||
In order to implement a new widget, the following JavaScript functions should be defined *(Note: each function is optional and can be implemented according to widget specific behaviour):*
|
In order to implement a new widget, the following JavaScript functions should be defined *(Note: each function is optional and can be implemented according to widget specific behaviour):*
|
||||||
|
|
||||||
|{:auto} **Function** | **Description** |
|
| {:auto} **Function** | **Description** |
|
||||||
|------------------------------------|----------------------------------------------------------------------------------------|
|
|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| ``` onInit() ``` | The first function which is called when widget is ready for initialization. Should be used to prepare widget DOM, process widget settings and initial subscription information. |
|
| ``` onInit() ``` | The first function which is called when widget is ready for initialization. Should be used to prepare widget DOM, process widget settings and initial subscription information. |
|
||||||
| ``` onDataUpdated() ``` | Called when the new data is available from the widget subscription. Latest data can be accessed from the <span trigger-style="fontSize: 16px;" trigger-text="<b>defaultSubscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> object of widget context (**ctx**). |
|
| ``` onDataUpdated() ``` | Called when the new data is available from the widget subscription. Latest data can be accessed from the <span trigger-style="fontSize: 16px;" trigger-text="<b>defaultSubscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> object of widget context (**ctx**). |
|
||||||
| ``` onResize() ``` | Called when widget container is resized. Latest width and height can be obtained from widget context (**ctx**). |
|
| ``` onResize() ``` | Called when widget container is resized. Latest width and height can be obtained from widget context (**ctx**). |
|
||||||
| ``` onEditModeChanged() ``` | Called when dashboard editing mode is changed. Latest mode is handled by isEdit property of **ctx**. |
|
| ``` onEditModeChanged() ``` | Called when dashboard editing mode is changed. Latest mode is handled by isEdit property of **ctx**. |
|
||||||
| ``` onMobileModeChanged() ``` | Called when dashboard view width crosses mobile breakpoint. Latest state is handled by isMobile property of **ctx**. |
|
| ``` onMobileModeChanged() ``` | Called when dashboard view width crosses mobile breakpoint. Latest state is handled by isMobile property of **ctx**. |
|
||||||
| ``` onDestroy() ``` | Called when widget element is destroyed. Should be used to cleanup all resources if necessary. |
|
| ``` onDestroy() ``` | Called when widget element is destroyed. Should be used to cleanup all resources if necessary. |
|
||||||
| ``` getSettingsSchema() ``` | Optional function returning widget settings schema json as alternative to **Settings schema** of settings section. |
|
| ``` getSettingsForm() ``` | Optional function returning widget settings form array as alternative to **Settings form** tab of settings section. |
|
||||||
| ``` getDataKeySettingsSchema() ``` | Optional function returning particular data key settings schema json as alternative to **Data key settings schema** tab of settings section. |
|
| ``` getDataKeySettingsForm() ``` | Optional function returning particular data key settings form array as alternative to **Data key settings form** tab of settings section. |
|
||||||
| ``` typeParameters() ``` | Returns [WidgetTypeParameters{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L151) object describing widget datasource parameters. See <span trigger-style="fontSize: 16px;" trigger-text="<b>Type parameters object</b>" tb-help-popup="widget/editor/widget_js_type_parameters_object"></span> | |
|
| ``` typeParameters() ``` | Returns [WidgetTypeParameters{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L151) object describing widget datasource parameters. See <span trigger-style="fontSize: 16px;" trigger-text="<b>Type parameters object</b>" tb-help-popup="widget/editor/widget_js_type_parameters_object"></span> | |
|
||||||
| ``` actionSources() ``` | Returns map describing available widget action sources ([WidgetActionSource{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L121)) used to define user actions. See <span trigger-style="fontSize: 16px;" trigger-text="<b>Action sources object</b>" tb-help-popup="widget/editor/widget_js_action_sources_object"></span> |
|
| ``` actionSources() ``` | Returns map describing available widget action sources ([WidgetActionSource{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L121)) used to define user actions. See <span trigger-style="fontSize: 16px;" trigger-text="<b>Action sources object</b>" tb-help-popup="widget/editor/widget_js_action_sources_object"></span> |
|
||||||
|
| ~~getSettingsSchema()~~ | **Deprecated**. Use getSettingsForm() function. |
|
||||||
|
| ~~getDataKeySettingsSchema()~~ | **Deprecated**. Use getDataKeySettingsForm() function. |
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@ For [Latest values{:target="_blank"}](${siteBaseUrl}/docs${docPlatformPrefix}/us
|
|||||||
label: 'Sin', // label of the dataKey. Used as display value (for ex. in the widget legend section)
|
label: 'Sin', // label of the dataKey. Used as display value (for ex. in the widget legend section)
|
||||||
color: '#ffffff', // color of the key. Can be used by widget to set color of the key data (for ex. lines in line chart or segments in the pie chart).
|
color: '#ffffff', // color of the key. Can be used by widget to set color of the key data (for ex. lines in line chart or segments in the pie chart).
|
||||||
funcBody: "", // only applicable for datasource with type "function" and "function" key type. Defines body of the function to generate simulated data.
|
funcBody: "", // only applicable for datasource with type "function" and "function" key type. Defines body of the function to generate simulated data.
|
||||||
settings: {} // dataKey specific settings with structure according to the defined Data key settings json schema. See "Settings schema section".
|
settings: {} // dataKey specific settings with structure according to the defined Data key settings form.
|
||||||
},
|
},
|
||||||
//...
|
//...
|
||||||
]
|
]
|
||||||
@ -72,7 +72,7 @@ For [Alarm widget{:target="_blank"}](${siteBaseUrl}/docs${docPlatformPrefix}/use
|
|||||||
type: 'alarm', // type of the dataKey. Only "alarm" in this case.
|
type: 'alarm', // type of the dataKey. Only "alarm" in this case.
|
||||||
label: 'Severity', // label of the dataKey. Used as display value (for ex. as a column title in the Alarms table)
|
label: 'Severity', // label of the dataKey. Used as display value (for ex. as a column title in the Alarms table)
|
||||||
color: '#ffffff', // color of the key. Can be used by widget to set color of the key data.
|
color: '#ffffff', // color of the key. Can be used by widget to set color of the key data.
|
||||||
settings: {} // dataKey specific settings with structure according to the defined Data key settings json schema. See "Settings schema section".
|
settings: {} // dataKey specific settings with structure according to the defined Data key settings form.
|
||||||
},
|
},
|
||||||
//...
|
//...
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1699,7 +1699,8 @@
|
|||||||
"clear-form-prompt": "Are you sure you want to remove all form properties?",
|
"clear-form-prompt": "Are you sure you want to remove all form properties?",
|
||||||
"import-form": "Import form from JSON",
|
"import-form": "Import form from JSON",
|
||||||
"export-form": "Export form to JSON",
|
"export-form": "Export form to JSON",
|
||||||
"form-json-file": "Form JSON file",
|
"json-file": "JSON file",
|
||||||
|
"json-content": "JSON content",
|
||||||
"invalid-form-json-file-error": "Unable to import form from JSON: Invalid form JSON data structure."
|
"invalid-form-json-file-error": "Unable to import form from JSON: Invalid form JSON data structure."
|
||||||
},
|
},
|
||||||
"asset-profile": {
|
"asset-profile": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user