39 changed files with 1773 additions and 154 deletions
@ -0,0 +1,26 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2021 The Thingsboard Authors |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<ng-container #markdownContainer> |
|||
</ng-container> |
|||
<div *ngIf="error && !fallbackToPlainMarkdown" style="color: #f00; font-size: 14px; |
|||
line-height: 28px; |
|||
background: #efefef;"> |
|||
{{error}} |
|||
</div> |
|||
<div #fallbackElement [fxShow]="error && fallbackToPlainMarkdown" class="tb-markdown-view" [ngClass]="markdownClass" [ngStyle]="style"> |
|||
</div> |
|||
@ -0,0 +1,19 @@ |
|||
<li><b>$event:</b> <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a></code> - The <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a> object. Usually a result of a mouse click event. |
|||
</li> |
|||
<li><b>widgetContext:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/models/widget-component.models.ts#L107" target="_blank">WidgetContext</a></code> - A reference to <a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/models/widget-component.models.ts#L107" target="_blank">WidgetContext</a> that has all necessary API |
|||
and data used by widget instance. |
|||
</li> |
|||
<li><b>entityId:</b> <code>string</code> - An optional string id of the target entity. |
|||
</li> |
|||
<li><b>entityName:</b> <code>string</code> - An optional string name of the target entity. |
|||
</li> |
|||
<li><b>additionalParams:</b> <code>{[key: string]: any}</code> - An optional key/value object holding additional entity parameters. |
|||
<span style="padding-left: 4px;" |
|||
tb-help-popup="widget/action/custom_additional_params" |
|||
tb-help-popup-placement="top" |
|||
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" |
|||
trigger-text="Read more"> |
|||
</span> |
|||
</li> |
|||
<li><b>entityLabel:</b> <code>string</code> - An optional string label of the target entity. |
|||
</li> |
|||
@ -1,46 +1,53 @@ |
|||
<li><b>additionalParams:</b> <code>{[key: string]: any}</code> - An optional key/value object holding additional entity parameters depending on widget type and action source: |
|||
<ul> |
|||
<li>Entities table widget (<i>On row click</i> or <i>Action cell button</i>) - <b>additionalParams:</b> <code>{ entity: <a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts#L61" target="_blank">EntityData</a> }</code>: |
|||
<ul> |
|||
<li><b>entity:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts#L61" target="_blank">EntityData</a></code> - An |
|||
<a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts#L61" target="_blank">EntityData</a> object |
|||
presenting basic entity properties (ex. <code>id</code>, <code>entityName</code>) and <br> provides access to other entity attributes/timeseries declared in widget datasource configuration. |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
<li>Alarms table widget (<i>On row click</i> or <i>Action cell button</i>) - <b>additionalParams:</b> <code>{ alarm: <a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/shared/models/alarm.models.ts#L108" target="_blank">AlarmDataInfo</a> }</code>: |
|||
<ul> |
|||
<li><b>alarm:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/shared/models/alarm.models.ts#L108" target="_blank">AlarmDataInfo</a></code> - An |
|||
<a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/shared/models/alarm.models.ts#L108" target="_blank">AlarmDataInfo</a> object |
|||
presenting basic alarm properties (ex. <code>type</code>, <code>severity</code>, <code>originator</code>, etc.) and <br> provides access to other alarm or originator entity fields/attributes/timeseries declared in widget datasource configuration. |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
<li>Timeseries table widget (<i>On row click</i> or <i>Action cell button</i>) - <b>additionalParams:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts#L80" target="_blank">TimeseriesRow</a></code>: |
|||
<ul> |
|||
<li><b>additionalParams:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts#L80" target="_blank">TimeseriesRow</a></code> - A |
|||
<a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts#L80" target="_blank">TimeseriesRow</a> object |
|||
presenting <code>formattedTs</code> (a string value of formatted timestamp) and <br> timeseries values for each column declared in widget datasource configuration. |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
<li>Entities hierarchy widget (<i>On node selected</i>) - <b>additionalParams:</b> <code>{ nodeCtx: <a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts#L35" target="_blank">HierarchyNodeContext</a> }</code>: |
|||
<ul> |
|||
<li><b>nodeCtx:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts#L35" target="_blank">HierarchyNodeContext</a></code> - An |
|||
<a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts#L35" target="_blank">HierarchyNodeContext</a> object |
|||
containing <code>entity</code> field holding basic entity properties <br> (ex. <code>id</code>, <code>name</code>, <code>label</code>) and <code>data</code> field holding other entity attributes/timeseries declared in widget datasource configuration. |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
<li>Pie - Flot widget (<i>On slice click</i>) - <b>additionalParams:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts#L62" target="_blank">TbFlotPlotItem</a></code>: |
|||
<ul> |
|||
<li><b>additionalParams:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts#L62" target="_blank">TbFlotPlotItem</a></code> - A |
|||
<a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts#L62" target="_blank">TbFlotPlotItem</a> object |
|||
containing <code>series</code> field with information about datasource and <br> data key of clicked pie slice. |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
<li><i>All other widgets</i> - does not provide <b>additionalParams</b> value. |
|||
</li> |
|||
</ul> |
|||
#### Additional params object |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
<b>additionalParams:</b> <code>{[key: string]: any}</code> |
|||
|
|||
An optional key/value object holding additional entity parameters depending on widget type and action source: |
|||
|
|||
<ul> |
|||
<li>Entities table widget (<i>On row click</i> or <i>Action cell button</i>) - <b>additionalParams:</b> <code>{ entity: <a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts#L61" target="_blank">EntityData</a> }</code>: |
|||
<ul> |
|||
<li><b>entity:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts#L61" target="_blank">EntityData</a></code> - An |
|||
<a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts#L61" target="_blank">EntityData</a> object |
|||
presenting basic entity properties (ex. <code>id</code>, <code>entityName</code>) and <br> provides access to other entity attributes/timeseries declared in widget datasource configuration. |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
<li>Alarms table widget (<i>On row click</i> or <i>Action cell button</i>) - <b>additionalParams:</b> <code>{ alarm: <a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/shared/models/alarm.models.ts#L108" target="_blank">AlarmDataInfo</a> }</code>: |
|||
<ul> |
|||
<li><b>alarm:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/shared/models/alarm.models.ts#L108" target="_blank">AlarmDataInfo</a></code> - An |
|||
<a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/shared/models/alarm.models.ts#L108" target="_blank">AlarmDataInfo</a> object |
|||
presenting basic alarm properties (ex. <code>type</code>, <code>severity</code>, <code>originator</code>, etc.) and <br> provides access to other alarm or originator entity fields/attributes/timeseries declared in widget datasource configuration. |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
<li>Timeseries table widget (<i>On row click</i> or <i>Action cell button</i>) - <b>additionalParams:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts#L80" target="_blank">TimeseriesRow</a></code>: |
|||
<ul> |
|||
<li><b>additionalParams:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts#L80" target="_blank">TimeseriesRow</a></code> - A |
|||
<a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts#L80" target="_blank">TimeseriesRow</a> object |
|||
presenting <code>formattedTs</code> (a string value of formatted timestamp) and <br> timeseries values for each column declared in widget datasource configuration. |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
<li>Entities hierarchy widget (<i>On node selected</i>) - <b>additionalParams:</b> <code>{ nodeCtx: <a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts#L35" target="_blank">HierarchyNodeContext</a> }</code>: |
|||
<ul> |
|||
<li><b>nodeCtx:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts#L35" target="_blank">HierarchyNodeContext</a></code> - An |
|||
<a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts#L35" target="_blank">HierarchyNodeContext</a> object |
|||
containing <code>entity</code> field holding basic entity properties <br> (ex. <code>id</code>, <code>name</code>, <code>label</code>) and <code>data</code> field holding other entity attributes/timeseries declared in widget datasource configuration. |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
<li>Pie - Flot widget (<i>On slice click</i>) - <b>additionalParams:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts#L62" target="_blank">TbFlotPlotItem</a></code>: |
|||
<ul> |
|||
<li><b>additionalParams:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts#L62" target="_blank">TbFlotPlotItem</a></code> - A |
|||
<a href="https://github.com/thingsboard/thingsboard/blob/e264f7b8ddff05bda85c4833bf497f47f447496e/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts#L62" target="_blank">TbFlotPlotItem</a> object |
|||
containing <code>series</code> field with information about datasource and <br> data key of clicked pie slice. |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
<li><i>All other widgets</i> - does not provide <b>additionalParams</b> value. |
|||
</li> |
|||
</ul> |
|||
|
|||
@ -0,0 +1,49 @@ |
|||
#### Get location function |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
*function getLocation($event, widgetContext, entityId, entityName, additionalParams, entityLabel): [number, number] | Observable<[number, number]>* |
|||
|
|||
A JavaScript function that should return location as array of two numbers (latitude, longitude) for further processing by mobile action.<br> |
|||
Usually location can be obtained from entity attributes/telemetry. |
|||
|
|||
**Parameters:** |
|||
|
|||
<ul> |
|||
{% include widget/action/custom_action_args %} |
|||
</ul> |
|||
|
|||
**Returns:** |
|||
|
|||
Latitude and longitude as array of two numbers or Observable of array of two numbers. For example ```[37.689, -122.433]```. |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
* Return location from entity attributes: |
|||
|
|||
```javascript |
|||
return getLocationFromEntityAttributes(); |
|||
|
|||
function getLocationFromEntityAttributes() { |
|||
if (entityId) { |
|||
return widgetContext.attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE', ['latitude', 'longitude']) |
|||
.pipe(widgetContext.rxjs |
|||
.map(function(attributeData) { |
|||
var res = [0,0]; |
|||
if (attributeData && attributeData.length === 2) { |
|||
res[0] = attributeData.filter(function (data) { return data.key === 'latitude'})[0].value; |
|||
res[1] = attributeData.filter(function (data) { return data.key === 'longitude'})[0].value; |
|||
} |
|||
return res; |
|||
} |
|||
) |
|||
); |
|||
} else { |
|||
return [0,0]; |
|||
} |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
@ -0,0 +1,48 @@ |
|||
#### Get phone number function |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
*function getPhoneNumber($event, widgetContext, entityId, entityName, additionalParams, entityLabel): number | string | Observable<number> | Observable<string>* |
|||
|
|||
A JavaScript function that should return phone number for further processing by mobile action.<br> |
|||
Usually phone number can be obtained from entity attributes/telemetry. |
|||
|
|||
**Parameters:** |
|||
|
|||
<ul> |
|||
{% include widget/action/custom_action_args %} |
|||
</ul> |
|||
|
|||
**Returns:** |
|||
|
|||
String or numeric value of phone number or Observable of string or numeric value. For example ```123456789```. |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
* Return phone number from entity attributes: |
|||
|
|||
```javascript |
|||
return getPhoneNumberFromEntityAttributes(); |
|||
|
|||
function getPhoneNumberFromEntityAttributes() { |
|||
if (entityId) { |
|||
return widgetContext.attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE', ['phone']) |
|||
.pipe(widgetContext.rxjs |
|||
.map(function(attributeData) { |
|||
var res = 0; |
|||
if (attributeData && attributeData.length === 1) { |
|||
res = attributeData[0].value; |
|||
} |
|||
return res; |
|||
} |
|||
) |
|||
); |
|||
} else { |
|||
return 0; |
|||
} |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
@ -0,0 +1,31 @@ |
|||
#### Handle empty result function |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
*function handleEmptyResult($event, widgetContext, entityId, entityName, additionalParams, entityLabel): void* |
|||
|
|||
An optional JavaScript function to handle empty result.<br>Usually this happens when user cancels the action (for ex. by pressing phone back button). |
|||
|
|||
**Parameters:** |
|||
|
|||
<ul> |
|||
{% include widget/action/custom_action_args %} |
|||
</ul> |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
* Display alert dialog with canceled action message: |
|||
|
|||
```javascript |
|||
showEmptyResultDialog('Action was canceled!'); |
|||
|
|||
function showEmptyResultDialog(message) { |
|||
setTimeout(function() { |
|||
widgetContext.dialogs.alert('Empty result', message).subscribe(); |
|||
}, 100); |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
@ -0,0 +1,33 @@ |
|||
#### Handle error function |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
*function handleError(error, $event, widgetContext, entityId, entityName, additionalParams, entityLabel): void* |
|||
|
|||
An optional JavaScript function to handle error occurred while mobile action execution. |
|||
|
|||
**Parameters:** |
|||
|
|||
<ul> |
|||
<li><b>error:</b> <code>string</code> - error message. |
|||
</li> |
|||
{% include widget/action/custom_action_args %} |
|||
</ul> |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
* Display alert dialog with error message: |
|||
|
|||
```javascript |
|||
showErrorDialog('Failed to perform action', error); |
|||
|
|||
function showErrorDialog(title, error) { |
|||
setTimeout(function() { |
|||
widgetContext.dialogs.alert(title, error).subscribe(); |
|||
}, 100); |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
@ -0,0 +1,92 @@ |
|||
#### Process image function |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
*function processImage(imageUrl, $event, widgetContext, entityId, entityName, additionalParams, entityLabel): void* |
|||
|
|||
A JavaScript function to process image obtained as a result of mobile action (take photo, take image from gallery, etc.). |
|||
|
|||
**Parameters:** |
|||
|
|||
<ul> |
|||
<li><b>imageUrl:</b> <code>string</code> - An image URL in base64 data format. |
|||
</li> |
|||
{% include widget/action/custom_action_args %} |
|||
</ul> |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
* Store image url data to entity attribute: |
|||
|
|||
```javascript |
|||
saveEntityImageAttribute('image', imageUrl); |
|||
|
|||
function saveEntityImageAttribute(attributeName, imageUrl) { |
|||
if (entityId) { |
|||
let attributes = [{ |
|||
key: attributeName, value: imageUrl |
|||
}]; |
|||
widgetContext.attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributes).subscribe( |
|||
function() { |
|||
widgetContext.showSuccessToast('Image attribute saved!'); |
|||
}, |
|||
function(error) { |
|||
widgetContext.dialogs.alert('Image attribute save failed', JSON.stringify(error)); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
* Display dialog with obtained image: |
|||
|
|||
```javascript |
|||
showImageDialog('Image', imageUrl); |
|||
|
|||
function showImageDialog(title, imageUrl) { |
|||
setTimeout(function() { |
|||
widgetContext.customDialog.customDialog(imageDialogTemplate, ImageDialogController, {imageUrl: imageUrl, title: title}).subscribe(); |
|||
}, 100); |
|||
} |
|||
|
|||
var imageDialogTemplate = |
|||
'<div aria-label="Image">' + |
|||
'<form #theForm="ngForm">' + |
|||
'<mat-toolbar fxLayout="row" color="primary">' + |
|||
'<h2>{{title}}</h2>' + |
|||
'<span fxFlex></span>' + |
|||
'<button mat-icon-button (click)="close()">' + |
|||
'<mat-icon>close</mat-icon>' + |
|||
'</button>' + |
|||
'</mat-toolbar>' + |
|||
'<div mat-dialog-content>' + |
|||
'<div class="mat-content mat-padding">' + |
|||
'<div fxLayout="column" fxFlex>' + |
|||
'<div style="padding-top: 20px;">' + |
|||
'<img [src]="imageUrl" style="height: 300px;"/>' + |
|||
'</div>' + |
|||
'</div>' + |
|||
'</div>' + |
|||
'</div>' + |
|||
'<div mat-dialog-actions fxLayout="row">' + |
|||
'<span fxFlex></span>' + |
|||
'<button mat-button (click)="close()" style="margin-right:20px;">Close</button>' + |
|||
'</div>' + |
|||
'</form>' + |
|||
'</div>'; |
|||
|
|||
function ImageDialogController(instance) { |
|||
let vm = instance; |
|||
vm.title = vm.data.title; |
|||
vm.imageUrl = vm.data.imageUrl; |
|||
vm.close = function () |
|||
{ |
|||
vm.dialogRef.close(null); |
|||
} |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
@ -0,0 +1,33 @@ |
|||
#### Process launch result function |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
*function processLaunchResult(launched, $event, widgetContext, entityId, entityName, additionalParams, entityLabel): void* |
|||
|
|||
An optional JavaScript function to process result of attempt to launch external mobile application (for ex. map application or phone call application). |
|||
|
|||
**Parameters:** |
|||
|
|||
<ul> |
|||
<li><b>launched:</b> <code>boolean</code> - boolean value indicating if the external application was successfully launched. |
|||
{% include widget/action/custom_action_args %} |
|||
</ul> |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
* Display alert dialog with external application launch status: |
|||
|
|||
```javascript |
|||
showLaunchStatusDialog('Application', launched); |
|||
|
|||
function showLaunchStatusDialog(title, status) { |
|||
setTimeout(function() { |
|||
widgetContext.dialogs.alert(title, status ? 'Successfully launched' : 'Failed to launch').subscribe(); |
|||
}, 100); |
|||
} |
|||
|
|||
{:copy-code} |
|||
``` |
|||
@ -0,0 +1,59 @@ |
|||
#### Process location function |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
*function processLocation(latitude, longitude, $event, widgetContext, entityId, entityName, additionalParams, entityLabel): void* |
|||
|
|||
A JavaScript function to process current location of the phone. |
|||
|
|||
**Parameters:** |
|||
|
|||
<ul> |
|||
<li><b>latitude:</b> <code>number</code> - phone location latitude. |
|||
</li> |
|||
<li><b>longitude:</b> <code>number</code> - phone location longitude. |
|||
</li> |
|||
{% include widget/action/custom_action_args %} |
|||
</ul> |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
* Display alert dialog with location data: |
|||
|
|||
```javascript |
|||
showLocationDialog('Location', latitude, longitude); |
|||
|
|||
function showLocationDialog(title, latitude, longitude) { |
|||
setTimeout(function() { |
|||
widgetContext.dialogs.alert(title, 'Latitude: '+latitude+'<br>Longitude: ' + longitude).subscribe(); |
|||
}, 100); |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
* Store phone location to entity attributes: |
|||
|
|||
```javascript |
|||
saveEntityLocationAttributes('latitude', 'longitude', latitude, longitude); |
|||
|
|||
function saveEntityLocationAttributes(latitudeAttributeName, longitudeAttributeName, latitude, longitude) { |
|||
if (entityId) { |
|||
let attributes = [ |
|||
{ key: latitudeAttributeName, value: latitude }, |
|||
{ key: longitudeAttributeName, value: longitude } |
|||
]; |
|||
widgetContext.attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributes).subscribe( |
|||
function() { |
|||
widgetContext.showSuccessToast('Location attributes saved!'); |
|||
}, |
|||
function(error) { |
|||
widgetContext.dialogs.alert('Location attributes save failed', JSON.stringify(error)); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
@ -0,0 +1,76 @@ |
|||
#### Process QR code function |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
*function processQrCode(code, format, $event, widgetContext, entityId, entityName, additionalParams, entityLabel): void* |
|||
|
|||
A JavaScript function to process result of barcode scanning. |
|||
|
|||
**Parameters:** |
|||
|
|||
<ul> |
|||
<li><b>code:</b> <code>string</code> - A string value of scanned barcode. |
|||
</li> |
|||
<li><b>format:</b> <code>string</code> - barcode format. See <a href="https://github.com/juliuscanute/qr_code_scanner/blob/c89f1eaddb94cca705d7e602a0c326e271680bf4/lib/src/types/barcode_format.dart#L1" target="_blank">BarcodeFormat</a> enum for possible values. |
|||
</li> |
|||
{% include widget/action/custom_action_args %} |
|||
</ul> |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
* Display alert dialog with scanned barcode: |
|||
|
|||
```javascript |
|||
showQrCodeDialog('Bar Code', code, format); |
|||
|
|||
function showQrCodeDialog(title, code, format) { |
|||
setTimeout(function() { |
|||
widgetContext.dialogs.alert(title, 'Code: ['+code+']<br>Format: ' + format).subscribe(); |
|||
}, 100); |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
* Parse code as a device claiming info (in this case ```{deviceName: string, secretKey: string}```)<br>and then claim device (see [Claiming devices{:target="_blank"}](${baseUrl}/docs/user-guide/claiming-devices/) for details): |
|||
|
|||
```javascript |
|||
var $scope = widgetContext.$scope; |
|||
var $injector = $scope.$injector; |
|||
var $translate = $injector.get(widgetContext.servicesMap.get('translate')); |
|||
var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); |
|||
var deviceNotFound = $translate.instant('widgets.input-widgets.claim-not-found'); |
|||
var failedClaimDevice = $translate.instant('widgets.input-widgets.claim-failed'); |
|||
var claimDeviceInfo = JSON.parse(code); |
|||
var deviceName = claimDeviceInfo.deviceName; |
|||
var secretKey = claimDeviceInfo.secretKey; |
|||
var claimRequest = { |
|||
secretKey: secretKey |
|||
}; |
|||
deviceService.claimDevice(deviceName, claimRequest, { ignoreErrors: true }).subscribe( |
|||
function (data) { |
|||
widgetContext.showSuccessToast('Device \'' + deviceName + '\' successfully claimed!'); |
|||
widgetContext.updateAliases(); |
|||
}, |
|||
function (error) { |
|||
if(error.status == 404) { |
|||
widgetContext.showErrorToast(deviceNotFound); |
|||
} else { |
|||
if (error.status !== 400 && error.error && error.error.message) { |
|||
showDialog('Failed to claim device', error.error.message); |
|||
} else { |
|||
widgetContext.showErrorToast(failedClaimDevice); |
|||
} |
|||
} |
|||
} |
|||
); |
|||
|
|||
function showDialog(title, error) { |
|||
setTimeout(function() { |
|||
widgetContext.dialogs.alert(title, error).subscribe(); |
|||
}, 100); |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
@ -0,0 +1,147 @@ |
|||
#### Sample Alarm widget |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
In the **Widgets Bundle** view, click the big “+” button at the bottom-right part of the screen and then click the “Create new widget type” button.<br> |
|||
Click the **Alarm Widget** button on the **Select widget type** popup.<br> |
|||
The **Widget Editor** will be opened, pre-populated with the content of the default **Alarm** template widget. |
|||
|
|||
- Replace content of the CSS tab in "Resources" section with the following one: |
|||
|
|||
```css |
|||
.my-alarm-table th { |
|||
text-align: left; |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following HTML code inside the HTML tab of "Resources" section: |
|||
|
|||
```html |
|||
<div fxFlex fxLayout="column" style="height: 100%;"> |
|||
<div>My first Alarm widget.</div> |
|||
<table class="my-alarm-table" style="width: 100%;"> |
|||
<thead> |
|||
<tr> |
|||
<th *ngFor="let dataKey of alarmSource?.dataKeys">{{dataKey.label}}</th> |
|||
<tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr *ngFor="let alarm of alarms"> |
|||
<td *ngFor="let dataKey of alarmSource?.dataKeys" |
|||
[ngStyle]="getAlarmCellStyle(alarm, dataKey)"> |
|||
{{getAlarmValue(alarm, dataKey)}} |
|||
</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following JSON content inside the "Settings schema" tab of **Settings schema section**: |
|||
|
|||
```json |
|||
{ |
|||
"schema": { |
|||
"type": "object", |
|||
"title": "AlarmTableSettings", |
|||
"properties": { |
|||
"alarmSeverityColorFunction": { |
|||
"title": "Alarm severity color function: f(severity)", |
|||
"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} |
|||
``` |
|||
|
|||
- Put the following JavaScript code inside the "JavaScript" section: |
|||
|
|||
```javascript |
|||
self.onInit = function() { |
|||
var pageLink = self.ctx.pageLink(); |
|||
|
|||
pageLink.typeList = self.ctx.widgetConfig.alarmTypeList; |
|||
pageLink.statusList = self.ctx.widgetConfig.alarmStatusList; |
|||
pageLink.severityList = self.ctx.widgetConfig.alarmSeverityList; |
|||
pageLink.searchPropagatedAlarms = self.ctx.widgetConfig.searchPropagatedAlarms; |
|||
|
|||
self.ctx.defaultSubscription.subscribeForAlarms(pageLink, null); |
|||
self.ctx.$scope.alarmSource = self.ctx.defaultSubscription.alarmSource; |
|||
|
|||
var alarmSeverityColorFunctionBody = self.ctx.settings.alarmSeverityColorFunction; |
|||
if (typeof alarmSeverityColorFunctionBody === 'undefined' || !alarmSeverityColorFunctionBody.length) { |
|||
alarmSeverityColorFunctionBody = "if(severity == 'CRITICAL') {return 'red';} else if (severity == 'MAJOR') {return 'orange';} else return 'green';"; |
|||
} |
|||
|
|||
var alarmSeverityColorFunction = null; |
|||
try { |
|||
alarmSeverityColorFunction = new Function('severity', alarmSeverityColorFunctionBody); |
|||
} catch (e) { |
|||
alarmSeverityColorFunction = null; |
|||
} |
|||
|
|||
self.ctx.$scope.getAlarmValue = function(alarm, dataKey) { |
|||
var alarmKey = dataKey.name; |
|||
if (alarmKey === 'originator') { |
|||
alarmKey = 'originatorName'; |
|||
} |
|||
var value = alarm[alarmKey]; |
|||
if (alarmKey === 'createdTime') { |
|||
return self.ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss'); |
|||
} else { |
|||
return value; |
|||
} |
|||
} |
|||
|
|||
self.ctx.$scope.getAlarmCellStyle = function(alarm, dataKey) { |
|||
var alarmKey = dataKey.name; |
|||
if (alarmKey === 'severity' && alarmSeverityColorFunction) { |
|||
var severity = alarm[alarmKey]; |
|||
var color = alarmSeverityColorFunction(severity); |
|||
return { |
|||
color: color |
|||
}; |
|||
} |
|||
return {}; |
|||
} |
|||
} |
|||
|
|||
self.onDataUpdated = function() { |
|||
self.ctx.$scope.alarms = self.ctx.defaultSubscription.alarms.data; |
|||
self.ctx.detectChanges(); |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section. |
|||
|
|||
 |
|||
|
|||
In this example, the **alarmSource** and **alarms** properties of <span trigger-style="fontSize: 16px;" trigger-text="<b>subscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> are assigned to **$scope** and become accessible within HTML template. |
|||
|
|||
Inside the HTML, a special [***ngFor**{:target="_blank"}](https://angular.io/api/common/NgForOf) structural angular directive is used in order to iterate over available alarm **dataKeys** of **alarmSource** and render corresponding columns. |
|||
|
|||
The table rows are rendered by iterating over **alarms** array and corresponding cells rendered by iterating over **dataKeys**. |
|||
|
|||
The function **getAlarmValue** is fetching alarm value and formatting **createdTime** alarm property using a [DatePipe{:target="_blank"}](https://angular.io/api/common/DatePipe) angular pipe accessible via **date** property of **ctx**. |
|||
|
|||
The function **getAlarmCellStyle** is used to assign custom cell styles for each alarm cell.<br>In this example, we introduced new settings property called **alarmSeverityColorFunction** that contains function body returning color depending on alarm severity. |
|||
|
|||
Inside the **getAlarmCellStyle** function there is corresponding invocation of **alarmSeverityColorFunction** with severity value in order to get color for alarm severity cell. |
|||
|
|||
Note that in this code **onDataUpdated** function is implemented in order to update **alarms** property with latest alarms from subscription and invoke change detection using **detectChanges()** function. |
|||
|
|||
<br/> |
|||
<br/> |
|||
@ -0,0 +1,67 @@ |
|||
#### Latest Values widget Example with gauge.js library |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
In this example, **Latest Values** gauge widget will be created using external [gauge.js{:target="_blank"}](http://bernii.github.io/gauge.js/) library. |
|||
|
|||
In the **Widgets Bundle** view, click the big “+” button at the bottom-right part of the screen, then click the “Create new widget type” button.<br> |
|||
Click the **Latest Values** button on the **Select widget type** popup.<br> |
|||
The **Widget Editor** will be opened, pre-populated with the content of default **Latest Values** template widget. |
|||
|
|||
- Open **Resources** tab and click "Add" then insert the following link: |
|||
|
|||
``` |
|||
https://bernii.github.io/gauge.js/dist/gauge.min.js |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Clear content of the CSS tab of "Resources" section. |
|||
- Put the following HTML code inside the HTML tab of "Resources" section: |
|||
|
|||
```html |
|||
<canvas id="my-gauge"></canvas> |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following JavaScript code inside the "JavaScript" section: |
|||
|
|||
```javascript |
|||
var canvasElement; |
|||
var gauge; |
|||
|
|||
self.onInit = function() { |
|||
canvasElement = $('#my-gauge', self.ctx.$container)[0]; |
|||
gauge = new Gauge(canvasElement); |
|||
gauge.minValue = -1000; |
|||
gauge.maxValue = 1000; |
|||
gauge.animationSpeed = 16; |
|||
self.onResize(); |
|||
} |
|||
|
|||
self.onResize = function() { |
|||
canvasElement.width = self.ctx.width; |
|||
canvasElement.height = self.ctx.height; |
|||
gauge.update(true); |
|||
gauge.render(); |
|||
} |
|||
|
|||
self.onDataUpdated = function() { |
|||
if (self.ctx.defaultSubscription.data[0].data.length) { |
|||
var value = self.ctx.defaultSubscription.data[0].data[0][1]; |
|||
gauge.set(value); |
|||
} |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section. |
|||
|
|||
 |
|||
|
|||
In this example, the external JS library API was used that becomes available after injecting the corresponding URL in **Resources** section. |
|||
|
|||
The value displayed was obtained from <span trigger-style="fontSize: 16px;" trigger-text="<b>subscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> **data** property for the first dataKey. |
|||
|
|||
<br/> |
|||
<br/> |
|||
@ -0,0 +1,112 @@ |
|||
#### Time-Series widget Example with Chart.js library |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
In this example, **Time-Series** line chart widget will be created using external [Chart.js{:target="_blank"}](https://www.chartjs.org/) library. |
|||
|
|||
In the **Widgets Bundle** view, click the big “+” button at the bottom-right part of the screen, then click the “Create new widget type” button.<br> |
|||
Click the **Time-Series** button on the **Select widget type** popup.<br> |
|||
The **Widget Editor** will be opened, pre-populated with the content of default **Time-Series** template widget. |
|||
|
|||
- Open **Resources** tab and click "Add" then insert the following link: |
|||
|
|||
``` |
|||
https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Clear content of the CSS tab of "Resources" section. |
|||
- Put the following HTML code inside the HTML tab of "Resources" section: |
|||
|
|||
```html |
|||
<canvas id="myChart"></canvas> |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following JavaScript code inside the "JavaScript" section: |
|||
|
|||
```javascript |
|||
var myChart; |
|||
|
|||
self.onInit = function() { |
|||
|
|||
var chartData = { |
|||
datasets: [] |
|||
}; |
|||
|
|||
for (var i=0; i < self.ctx.data.length; i++) { |
|||
var dataKey = self.ctx.data[i].dataKey; |
|||
var dataset = { |
|||
label: dataKey.label, |
|||
data: [], |
|||
borderColor: dataKey.color, |
|||
fill: false |
|||
}; |
|||
chartData.datasets.push(dataset); |
|||
} |
|||
|
|||
var options = { |
|||
maintainAspectRatio: false, |
|||
legend: { |
|||
display: false |
|||
}, |
|||
scales: { |
|||
xAxes: [{ |
|||
type: 'time', |
|||
ticks: { |
|||
maxRotation: 0, |
|||
autoSkipPadding: 30 |
|||
} |
|||
}] |
|||
} |
|||
}; |
|||
|
|||
var canvasElement = $('#myChart', self.ctx.$container)[0]; |
|||
var canvasCtx = canvasElement.getContext('2d'); |
|||
myChart = new Chart(canvasCtx, { |
|||
type: 'line', |
|||
data: chartData, |
|||
options: options |
|||
}); |
|||
self.onResize(); |
|||
} |
|||
|
|||
self.onResize = function() { |
|||
myChart.resize(); |
|||
} |
|||
|
|||
self.onDataUpdated = function() { |
|||
for (var i = 0; i < self.ctx.data.length; i++) { |
|||
var datasourceData = self.ctx.data[i]; |
|||
var dataSet = datasourceData.data; |
|||
myChart.data.datasets[i].data.length = 0; |
|||
var data = myChart.data.datasets[i].data; |
|||
for (var d = 0; d < dataSet.length; d++) { |
|||
var tsValuePair = dataSet[d]; |
|||
var ts = tsValuePair[0]; |
|||
var value = tsValuePair[1]; |
|||
data.push({t: ts, y: value}); |
|||
} |
|||
} |
|||
myChart.options.scales.xAxes[0].ticks.min = self.ctx.timeWindow.minTime; |
|||
myChart.options.scales.xAxes[0].ticks.max = self.ctx.timeWindow.maxTime; |
|||
myChart.update(); |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section. |
|||
|
|||
 |
|||
|
|||
In this example, the external JS library API was used that becomes available after injecting the corresponding URL in **Resources** section. |
|||
|
|||
Initially chart datasets prepared using configured dataKeys from **data** property of **ctx**. |
|||
|
|||
In the **onDataUpdated** function datasources data converted to Chart.js line chart format and pushed to chart datasets. |
|||
|
|||
Please note that xAxis (time axis) is limited to current timewindow bounds obtained from **timeWindow** property of **ctx**. |
|||
|
|||
<br/> |
|||
<br/> |
|||
@ -0,0 +1,47 @@ |
|||
#### Sample Latest Values widget |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
In the **Widgets Bundle** view, click the big “+” button at the bottom-right part of the screen and then click the “Create new widget type” button.<br> |
|||
Click the **Latest Values** button on the **Select widget type** popup.<br> |
|||
The **Widget Editor** will open, pre-populated with the content of the default **Latest Values** template widget. |
|||
|
|||
- Clear content of the CSS tab of "Resources" section. |
|||
- Put the following HTML code inside the HTML tab of "Resources" section: |
|||
|
|||
```html |
|||
<div fxFlex fxLayout="column" style="height: 100%;" fxLayoutAlign="center stretch"> |
|||
<div>My first latest values widget.</div> |
|||
<div fxFlex fxLayout="row" *ngFor="let dataKeyData of data" fxLayoutAlign="space-around center"> |
|||
<div>{{dataKeyData.dataKey.label}}:</div> |
|||
<div>{{(dataKeyData.data[0] && dataKeyData.data[0][0]) | date : 'yyyy-MM-dd HH:mm:ss' }}</div> |
|||
<div>{{dataKeyData.data[0] && dataKeyData.data[0][1]}}</div> |
|||
</div> |
|||
</div> |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following JavaScript code inside the "JavaScript" section: |
|||
|
|||
```javascript |
|||
self.onInit = function() { |
|||
self.ctx.$scope.data = self.ctx.defaultSubscription.data; |
|||
} |
|||
|
|||
self.onDataUpdated = function() { |
|||
self.ctx.detectChanges(); |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section. |
|||
|
|||
 |
|||
|
|||
In this example, the **data** property of <span trigger-style="fontSize: 16px;" trigger-text="<b>subscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> is assigned to the **$scope** and becomes accessible within the HTML template. |
|||
|
|||
Inside the HTML, a special [***ngFor**{:target="_blank"}](https://angular.io/api/common/NgForOf) structural angular directive is used in order to iterate over available dataKeys & datapoints then render latest values with their corresponding timestamps. |
|||
|
|||
<br/> |
|||
<br/> |
|||
@ -0,0 +1,187 @@ |
|||
#### Sample RPC (Control) widget |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
In the **Widgets Bundle** view, click the big “+” button at the bottom-right part of the screen and then click the “Create new widget type” button.<br> |
|||
Click the **Control Widget** button on the **Select widget type** popup.<br> |
|||
The **Widget Editor** will open, pre-populated with default **Control** template widget content. |
|||
|
|||
- Clear content of the CSS tab of "Resources" section. |
|||
- Put the following HTML code inside the HTML tab of "Resources" section: |
|||
|
|||
```html |
|||
<form #rpcForm="ngForm" (submit)="sendCommand()"> |
|||
<div class="mat-content mat-padding" fxLayout="column"> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label>RPC method</mat-label> |
|||
<input matInput required name="rpcMethod" #rpcMethodField="ngModel" [(ngModel)]="rpcMethod"/> |
|||
<mat-error *ngIf="rpcMethodField.hasError('required')"> |
|||
RPC method name is required. |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label>RPC params</mat-label> |
|||
<input matInput required name="rpcParams" #rpcParamsField="ngModel" [(ngModel)]="rpcParams"/> |
|||
<mat-error *ngIf="rpcParamsField.hasError('required')"> |
|||
RPC params is required. |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<button [disabled]="rpcForm.invalid || !rpcForm.dirty" mat-raised-button color="primary" type="submit" > |
|||
Send RPC command |
|||
</button> |
|||
<div> |
|||
<label>RPC command response</label> |
|||
<div style="width: 100%; height: 100px; border: solid 2px gray" [innerHTML]="rpcCommandResponse"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</form> |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following JSON content inside the "Settings schema" tab of **Settings schema section**: |
|||
|
|||
```json |
|||
{ |
|||
"schema": { |
|||
"type": "object", |
|||
"title": "Settings", |
|||
"properties": { |
|||
"oneWayElseTwoWay": { |
|||
"title": "Is One Way Command", |
|||
"type": "boolean", |
|||
"default": true |
|||
}, |
|||
"requestTimeout": { |
|||
"title": "RPC request timeout", |
|||
"type": "number", |
|||
"default": 500 |
|||
} |
|||
}, |
|||
"required": [] |
|||
}, |
|||
"form": [ |
|||
"oneWayElseTwoWay", |
|||
"requestTimeout" |
|||
] |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following JavaScript code inside the "JavaScript" section: |
|||
|
|||
```javascript |
|||
self.onInit = function() { |
|||
|
|||
self.ctx.$scope.sendCommand = function() { |
|||
var rpcMethod = self.ctx.$scope.rpcMethod; |
|||
var rpcParams = self.ctx.$scope.rpcParams; |
|||
var timeout = self.ctx.settings.requestTimeout; |
|||
var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ? true : false; |
|||
|
|||
var commandObservable; |
|||
if (oneWayElseTwoWay) { |
|||
commandObservable = self.ctx.controlApi.sendOneWayCommand(rpcMethod, rpcParams, timeout); |
|||
} else { |
|||
commandObservable = self.ctx.controlApi.sendTwoWayCommand(rpcMethod, rpcParams, timeout); |
|||
} |
|||
commandObservable.subscribe( |
|||
function (response) { |
|||
if (oneWayElseTwoWay) { |
|||
self.ctx.$scope.rpcCommandResponse = "Command was successfully received by device.<br> No response body because of one way command mode."; |
|||
} else { |
|||
self.ctx.$scope.rpcCommandResponse = "Response from device:<br>"; |
|||
self.ctx.$scope.rpcCommandResponse += JSON.stringify(response, undefined, 2); |
|||
} |
|||
self.ctx.detectChanges(); |
|||
}, |
|||
function (rejection) { |
|||
self.ctx.$scope.rpcCommandResponse = "Failed to send command to the device:<br>" |
|||
self.ctx.$scope.rpcCommandResponse += "Status: " + rejection.status + "<br>"; |
|||
self.ctx.$scope.rpcCommandResponse += "Status text: '" + rejection.statusText + "'"; |
|||
self.ctx.detectChanges(); |
|||
} |
|||
|
|||
); |
|||
} |
|||
|
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Fill **Widget title** field with widget type name, for ex. "My first control widget". |
|||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section. |
|||
- Click dashboard edit button on the preview section to change the size of the resulting widget. Then click dashboard apply button. The final widget should look like the image below. |
|||
|
|||
 |
|||
|
|||
- Click the **Save** button on the **Widget Editor Toolbar** to save widget type. |
|||
|
|||
To test how this widget performs RPC commands, we will need to place it in a dashboard then bind it to a device working with RPC commands. To do this, perform the following steps: |
|||
|
|||
- Login as Tenant administrator. |
|||
- Navigate to **Devices** and create new device with some name, for ex. "My RPC Device". |
|||
- Open device details and click "Copy Access Token" button to copy device access token to clipboard. |
|||
- Download [mqtt-js-rpc-from-server.sh{:target="_blank"}](${baseUrl}/docs/reference/resources/mqtt-js-rpc-from-server.sh) and [mqtt-js-rpc-from-server.js{:target="_blank"}](${baseUrl}/docs/reference/resources/mqtt-js-rpc-from-server.js). Place these files in a folder. |
|||
Edit **mqtt-js-rpc-from-server.sh** - replace **$ACCESS_TOKEN** with your device access token from the clipboard. And install mqtt client library. |
|||
- Run **mqtt-js-rpc-from-server.sh** script. You should see a "connected" message in the console. |
|||
- Navigate to **Dashboards** and create a new dashboard with some name, for ex. "My first control dashboard". Open this dashboard. |
|||
- Click dashboard "edit" button. In the dashboard edit mode, click the "Entity aliases" button located on the dashboard toolbar. |
|||
|
|||
 |
|||
|
|||
- Inside **Entity aliases** popup click "Add alias". |
|||
- Fill "Alias name" field, for ex. "My RPC Device Alias". |
|||
- Select "Entity list" in "Filter type" field. |
|||
- Choose "Device" in "Type" field. |
|||
- Select your device in "Entity list" field. In this example "My RPC Device". |
|||
|
|||
 |
|||
|
|||
- Click "Add" and then "Save" in **Entity aliases**. |
|||
- Click dashboard "+" button then click "Create new widget" button. |
|||
|
|||
 |
|||
|
|||
- Then select **Widget Bundle** where your RPC widget was saved. Select "Control widget" tab. |
|||
- Click your widget. In this example, "My first control widget". |
|||
- From **Add Widget** popup, select your device alias in **Target device** section. In this example "My RPC Device Alias". |
|||
- Click **Add**. Your Control widget will appear in the dashboard. Click dashboard **Apply changes** button to save dashboard and leave editing mode. |
|||
- Fill **RPC method** field with RPC method name. For ex. "TestMethod". |
|||
- Fill **RPC params** field with RPC params. For ex. "{ param1: "value1" }". |
|||
- Click **Send RPC command** button. You should see the following response in the widget. |
|||
|
|||
 |
|||
|
|||
The following output should be printed in the device console: |
|||
|
|||
```bash |
|||
request.topic: v1/devices/me/rpc/request/0 |
|||
request.body: {"method":"TestMethod","params":"{ param1: \"value1\" }"} |
|||
``` |
|||
|
|||
In order to test "Two way" RPC command mode, we need to change the corresponding widget settings property. To do this, perform the following steps: |
|||
|
|||
- Click dashboard "edit" button. In dashboard edit mode, click **Edit widget** button located in the header of Control widget. |
|||
- In the widget details, view select "Advanced" tab and uncheck "Is One Way Command" checkbox. |
|||
|
|||
 |
|||
|
|||
- Click **Apply changes** button on the widget details header. Close details and click dashboard **Apply changes** button. |
|||
- Fill widget fields with RPC method name and params like in previous steps. |
|||
Click **Send RPC command** button. You should see the following response in the widget. |
|||
|
|||
 |
|||
|
|||
- stop **mqtt-js-rpc-from-server.sh** script. |
|||
Click **Send RPC command** button. You should see the following response in the widget. |
|||
|
|||
 |
|||
|
|||
In this example, **controlApi** is used to send RPC commands. Additionally, custom widget settings were introduced in order to configure RPC command mode and RPC request timeout. |
|||
|
|||
The response from the device is handled by **commandObservable**. It has success and failed callbacks with corresponding response, or rejection objects containing information about request execution result. |
|||
|
|||
<br> |
|||
<br> |
|||
@ -0,0 +1,72 @@ |
|||
#### Sample Static widget |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
In the **Widgets Bundle** view, click the big “+” button at the bottom-right part of the screen and then click the “Create new widget type” button.<br> |
|||
Click the **Static Widget** button on the **Select widget type** popup.<br> |
|||
The **Widget Editor** will be opened pre-populated with the content of default **Static** template widget. |
|||
|
|||
- Put the following HTML code inside the HTML tab of "Resources" section: |
|||
|
|||
```html |
|||
<div fxFlex fxLayout="column" style="height: 100%;" fxLayoutAlign="space-around stretch"> |
|||
<h3 style="text-align: center;">My first static widget.</h3> |
|||
<button mat-raised-button color="primary" (click)="showAlert()">Click me</button> |
|||
</div> |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following JSON content inside the "Settings schema" tab of **Settings schema section**: |
|||
|
|||
```json |
|||
{ |
|||
"schema": { |
|||
"type": "object", |
|||
"title": "Settings", |
|||
"properties": { |
|||
"alertContent": { |
|||
"title": "Alert content", |
|||
"type": "string", |
|||
"default": "Content derived from alertContent property of widget settings." |
|||
} |
|||
} |
|||
}, |
|||
"form": [ |
|||
"alertContent" |
|||
] |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following JavaScript code inside the "JavaScript" section: |
|||
|
|||
```javascript |
|||
self.onInit = function() { |
|||
|
|||
self.ctx.$scope.showAlert = function() { |
|||
var alertContent = self.ctx.settings.alertContent; |
|||
if (!alertContent) { |
|||
alertContent = "Content derived from alertContent property of widget settings."; |
|||
} |
|||
window.alert(alertContent); |
|||
}; |
|||
|
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Click the **Run** button on the **Widget Editor Toolbar** to see the resulting **Widget preview** section. |
|||
|
|||
 |
|||
|
|||
This is just a static HTML widget. There is no subscription data and no special widget API was used. |
|||
|
|||
Only custom **showAlert** function was implemented showing an alert with the content of **alertContent** property of widget settings. |
|||
|
|||
You can switch to dashboard edit mode in **Widget preview** section and change value of **alertContent** by changing widget settings in the "Advanced" tab of widget details. |
|||
|
|||
Then you can see that the new alert content is displayed. |
|||
|
|||
<br/> |
|||
<br/> |
|||
@ -0,0 +1,93 @@ |
|||
#### Sample Time-Series widget |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
In the **Widgets Bundle** view, click the big “+” button at the bottom-right part of the screen, then click the “Create new widget type” button.<br> |
|||
Click the **Time-Series** button on the **Select widget type** popup.<br> |
|||
The **Widget Editor** will open, pre-populated with default **Time-Series** template widget content. |
|||
|
|||
- Replace content of the CSS tab in "Resources" section with the following one: |
|||
|
|||
```css |
|||
.my-data-table th { |
|||
text-align: left; |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following HTML code inside the HTML tab of "Resources" section: |
|||
|
|||
```html |
|||
<mat-tab-group style="height: 100%;"> |
|||
<mat-tab *ngFor="let datasource of datasources; let $dsIndex = index" label="{{datasource.name}}"> |
|||
<table class="my-data-table" style="width: 100%;"> |
|||
<thead> |
|||
<tr> |
|||
<th>Timestamp</th> |
|||
<th *ngFor="let dataKeyData of datasourceData[$dsIndex]">{{dataKeyData.dataKey.label}}</th> |
|||
<tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr *ngFor="let data of datasourceData[$dsIndex][0].data; let $dataIndex = index"> |
|||
<td>{{data[0] | date : 'yyyy-MM-dd HH:mm:ss'}}</td> |
|||
<td *ngFor="let dataKeyData of datasourceData[$dsIndex]">{{dataKeyData.data[$dataIndex] && dataKeyData.data[$dataIndex][1]}}</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</mat-tab> |
|||
</mat-tab-group> |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Put the following JavaScript code inside the "JavaScript" section: |
|||
|
|||
```javascript |
|||
self.onInit = function() { |
|||
self.ctx.widgetTitle = 'My first Time-Series widget'; |
|||
self.ctx.$scope.datasources = self.ctx.defaultSubscription.datasources; |
|||
self.ctx.$scope.data = self.ctx.defaultSubscription.data; |
|||
|
|||
self.ctx.$scope.datasourceData = []; |
|||
|
|||
var currentDatasource = null; |
|||
var currentDatasourceIndex = -1; |
|||
|
|||
for (var i=0;i<self.ctx.$scope.data.length;i++) { |
|||
var dataKeyData = self.ctx.$scope.data[i]; |
|||
if (dataKeyData.datasource != currentDatasource) { |
|||
currentDatasource = dataKeyData.datasource |
|||
currentDatasourceIndex++; |
|||
self.ctx.$scope.datasourceData[currentDatasourceIndex] = []; |
|||
|
|||
} |
|||
self.ctx.$scope.datasourceData[currentDatasourceIndex].push(dataKeyData); |
|||
} |
|||
self.ctx.updateWidgetParams(); |
|||
|
|||
} |
|||
|
|||
self.onDataUpdated = function() { |
|||
self.ctx.detectChanges(); |
|||
} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section. |
|||
|
|||
 |
|||
|
|||
In this example, the <span trigger-style="fontSize: 16px;" trigger-text="<b>subscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> **datasources** and **data** properties are assigned to **$scope** and become accessible within the HTML template. |
|||
|
|||
The **$scope.datasourceData** property is introduced to map datasource specific dataKeys data by datasource index for flexible access within the HTML template. |
|||
|
|||
Inside the HTML, a special [***ngFor**{:target="_blank"}](https://angular.io/api/common/NgForOf) structural angular directive is used in order to iterate over available datasources and render corresponding tabs. |
|||
|
|||
Inside each tab, the table is rendered using dataKeys obtained from **datasourceData** scope property accessed by datasource index.<br> |
|||
|
|||
Each table renders columns by iterating over all **dataKeyData** objects and renders all available datapoints by iterating over **data** array of each **dataKeyData** to render timestamps and values. |
|||
|
|||
Note that in this code, **onDataUpdated** function is implemented with a call to **detectChanges** function necessary to perform new change detection cycle when new data is received. |
|||
|
|||
<br/> |
|||
<br/> |
|||
@ -0,0 +1,17 @@ |
|||
#### Action sources object |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
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)) to which user actions can be assigned. It has the following structure: |
|||
|
|||
```javascript |
|||
return { |
|||
'headerButton': { // Action source Id (unique action source identificator) |
|||
name: 'widget-action.header-button', // Display name of action source, used in widget settings ('Actions' tab). |
|||
value: 'headerButton', // Action source Id |
|||
multiple: true // Boolean property indicating if this action source supports multiple action definitions |
|||
// (for ex. multiple buttons in one cell, or only one action can by assigned on table row click.) |
|||
} |
|||
}; |
|||
``` |
|||
@ -0,0 +1,62 @@ |
|||
#### Using existing JavaScript/Typescript code |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
Another approach of creating widgets is to use existing bundled JavaScript/Typescript code. |
|||
|
|||
In this case, you can create own TypeScript class or Angular component and bundle it into the ThingsBoard UI code. |
|||
|
|||
In order to make this code accessible within the widget, you need to register corresponding Angular module or inject TypeScript class to a global variable (for ex. window object). |
|||
|
|||
Some ThingsBoard widgets already use this approach. Take a look at the [widget-component-service.ts{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts#L140) |
|||
or [widget-components.module.ts{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts#L50). <br> |
|||
Here you can find how some bundled classes or components are registered for later use in ThingsBoard widgets. |
|||
|
|||
For example "Timeseries - Flot" widget (from "Charts" Widgets Bundle) uses [**TbFlot**{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts#L73) TypeScript class which is injected as window property inside **widget-component-service.ts**: |
|||
|
|||
```typescript |
|||
... |
|||
|
|||
const widgetModulesTasks: Observable<any>[] = []; |
|||
... |
|||
|
|||
widgetModulesTasks.push(from(import('@home/components/widget/lib/flot-widget')).pipe( |
|||
tap((mod) => { |
|||
(window as any).TbFlot = mod.TbFlot; |
|||
})) |
|||
); |
|||
... |
|||
|
|||
``` |
|||
|
|||
Another example is "Timeseries table" widget (from "Cards" Widgets Bundle) that uses Angular component [**tb-timeseries-table-widget**{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts#L107)<br>which is registered as dependency of **WidgetComponentsModule** Angular module inside **widget-components.module.ts**. |
|||
Thereby this component becomes available for use inside the widget template HTML. |
|||
|
|||
```typescript |
|||
... |
|||
|
|||
import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component'; |
|||
|
|||
... |
|||
|
|||
@NgModule({ |
|||
declarations: |
|||
[ |
|||
... |
|||
TimeseriesTableWidgetComponent, |
|||
... |
|||
], |
|||
... |
|||
exports: [ |
|||
... |
|||
TimeseriesTableWidgetComponent, |
|||
... |
|||
], |
|||
... |
|||
}) |
|||
export class WidgetComponentsModule { } |
|||
``` |
|||
|
|||
<br> |
|||
<br> |
|||
@ -0,0 +1,135 @@ |
|||
#### Widget type JavaScript code |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
All widget related JavaScript code according to the [Widget API{:target="_blank"}](${baseUrl}/docs/user-guide/contribution/widgets-development/#basic-widget-api). |
|||
The built-in variable **self** is a reference to the widget instance.<br> |
|||
Each widget function should be defined as a property of the **self** variable. |
|||
**self** variable has property **ctx** of type [WidgetContext{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/models/widget-component.models.ts#L107) - a reference to widget context that has all necessary API and data used by widget instance. |
|||
|
|||
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** | |
|||
|------------------------------------|----------------------------------------------------------------------------------------| |
|||
| ``` 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**). | |
|||
| ``` 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**. | |
|||
| ``` 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. | |
|||
| ``` getSettingsSchema() ``` | Optional function returning widget settings schema json as alternative to **Settings schema** of settings section. | |
|||
| ``` getDataKeySettingsSchema() ``` | Optional function returning particular data key settings schema json as alternative to **Data key settings schema** 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> | | |
|||
| ``` 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> | |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Creating simple widgets |
|||
|
|||
The tutorials below show how to create minimal widgets of each type. |
|||
In order to minimize the amount of code, the Angular framework will be used, on which ThingsBoard UI is actually based. |
|||
By the way, you can always use pure JavaScript or jQuery API in your widget code. |
|||
|
|||
<br> |
|||
|
|||
<div style="padding-left: 64px;" |
|||
tb-help-popup="widget/editor/examples/latest_values_widget" |
|||
tb-help-popup-placement="top" |
|||
trigger-style="font-size: 16px;" |
|||
trigger-text="Sample Latest Values widget"> |
|||
</div> |
|||
|
|||
<br> |
|||
|
|||
<div style="padding-left: 64px;" |
|||
tb-help-popup="widget/editor/examples/timeseries_widget" |
|||
tb-help-popup-placement="top" |
|||
trigger-style="font-size: 16px;" |
|||
trigger-text="Sample Time-Series widget"> |
|||
</div> |
|||
|
|||
<br> |
|||
|
|||
<div style="padding-left: 64px;" |
|||
tb-help-popup="widget/editor/examples/rpc_widget" |
|||
tb-help-popup-placement="top" |
|||
trigger-style="font-size: 16px;" |
|||
trigger-text="Sample RPC (Control) widget"> |
|||
</div> |
|||
|
|||
<br> |
|||
|
|||
<div style="padding-left: 64px;" |
|||
tb-help-popup="widget/editor/examples/alarm_widget" |
|||
tb-help-popup-placement="top" |
|||
trigger-style="font-size: 16px;" |
|||
trigger-text="Sample Alarm widget"> |
|||
</div> |
|||
|
|||
<br> |
|||
|
|||
<div style="padding-left: 64px;" |
|||
tb-help-popup="widget/editor/examples/static_widget" |
|||
tb-help-popup-placement="top" |
|||
trigger-style="font-size: 16px;" |
|||
trigger-text="Sample Static widget"> |
|||
</div> |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Integrating existing code to create widget definition |
|||
|
|||
Below are some examples demonstrating how external JavaScript libraries or existing code can be reused/integrated to create new widgets. |
|||
|
|||
###### Using external JavaScript library |
|||
|
|||
<br> |
|||
|
|||
<div style="padding-left: 64px;" |
|||
tb-help-popup="widget/editor/examples/ext_latest_values_example" |
|||
tb-help-popup-placement="top" |
|||
trigger-style="font-size: 16px;" |
|||
trigger-text="Latest Values widget Example with gauge.js library"> |
|||
</div> |
|||
|
|||
<br> |
|||
|
|||
<div style="padding-left: 64px;" |
|||
tb-help-popup="widget/editor/examples/ext_timeseries_example" |
|||
tb-help-popup-placement="top" |
|||
trigger-style="font-size: 16px;" |
|||
trigger-text="Time-Series widget Example with Chart.js library"> |
|||
</div> |
|||
|
|||
###### Using existing JavaScript/Typescript code |
|||
|
|||
<br> |
|||
|
|||
<div style="padding-left: 64px;" |
|||
tb-help-popup="widget/editor/widget_js_existing_code" |
|||
tb-help-popup-placement="top" |
|||
trigger-style="font-size: 16px;" |
|||
trigger-text="Read more"> |
|||
</div> |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Widget code debugging tips |
|||
|
|||
The most simple method of debugging is Web console output. |
|||
Just place [**console.log(...)**{:target="_blank"}](https://developer.mozilla.org/en-US/docs/Web/API/Console/log) function inside any part of widget JavaScript code. |
|||
Then click **Run** button to restart widget code and observe debug information in the Web console. |
|||
|
|||
Another and most effective method of debugging is to invoke browser debugger. |
|||
Put [**debugger;**{:target="_blank"}](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger) statement into the place of widget code you are interested in and then click **Run** button to restart widget code. |
|||
Browser debugger (if enabled) will automatically pause code execution at the debugger statement and you will be able to analyze script execution using browser debugging tools. |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Further reading |
|||
|
|||
For more information read [Widgets Development Guide{:target="_blank"}](${baseUrl}/docs/user-guide/contribution/widgets-development). |
|||
|
|||
<br> |
|||
<br> |
|||
@ -0,0 +1,113 @@ |
|||
#### Subscription object |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
The widget subscription object is instance of [IWidgetSubscription{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/core/api/widget-api.models.ts#L264") and contains all subscription information, including current data, according to the [widget type{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#widget-types). |
|||
|
|||
Depending on widget type, subscription object provides different data structures. |
|||
For [Latest values{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#latest-values) and [Time-series{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#time-series) widget types, it provides the following properties: |
|||
|
|||
- **datasources** - array of datasources (Array<[Datasource{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L279)>) used by this subscription, using the following structure: |
|||
|
|||
```javascript |
|||
datasources = [ |
|||
{ // datasource |
|||
type: 'entity',// type of the datasource. Can be "function" or "entity" |
|||
name: 'name', // name of the datasource (in case of "entity" usually Entity name) |
|||
aliasName: 'aliasName', // name of the alias used to resolve this particular datasource Entity |
|||
entityName: 'entityName', // name of the Entity used as datasource |
|||
entityType: 'DEVICE', // datasource Entity type (for ex. "DEVICE", "ASSET", "TENANT", etc.) |
|||
entityId: '943b8cd0-576a-11e7-824c-0b1cb331ec92', // entity identificator presented as string uuid. |
|||
dataKeys: [ // array of keys (Array<DataKey>) (attributes or timeseries) of the entity used to fetch data |
|||
{ // dataKey |
|||
name: 'name', // the name of the particular entity attribute/timeseries |
|||
type: 'timeseries', // type of the dataKey. Can be "timeseries", "attribute" or "function" |
|||
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). |
|||
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". |
|||
}, |
|||
//... |
|||
] |
|||
}, |
|||
//... |
|||
] |
|||
``` |
|||
|
|||
- **data** - array of latest data (Array<[DatasourceData{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L310)>) received in scope of this subscription, using the following structure: |
|||
|
|||
```javascript |
|||
data = [ |
|||
{ |
|||
datasource: {}, // datasource object of this data. See datasource structure above. |
|||
dataKey: {}, // dataKey for which the data is held. See dataKey structure above. |
|||
data: [ // array of data points |
|||
[ // data point |
|||
1498150092317, // unix timestamp of datapoint in milliseconds |
|||
1, // value, can be either string, numeric or boolean |
|||
], |
|||
//... |
|||
] |
|||
}, |
|||
//... |
|||
] |
|||
``` |
|||
|
|||
For [Alarm widget{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#alarm-widget) type it provides the following properties: |
|||
|
|||
- **alarmSource** - ([Datasource{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L279)) information about entity for which alarms are fetched, using the following structure: |
|||
|
|||
```javascript |
|||
alarmSource = { |
|||
type: 'entity',// type of the alarm source. Can be "function" or "entity" |
|||
name: 'name', // name of the alarm source (in case of "entity" usually Entity name) |
|||
aliasName: 'aliasName', // name of the alias used to resolve this particular alarm source Entity |
|||
entityName: 'entityName', // name of the Entity used as alarm source |
|||
entityType: 'DEVICE', // alarm source Entity type (for ex. "DEVICE", "ASSET", "TENANT", etc.) |
|||
entityId: '943b8cd0-576a-11e7-824c-0b1cb331ec92', // entity identificator presented as string uuid. |
|||
dataKeys: [ // array of keys indicating alarm fields used to display alarms data |
|||
{ // dataKey |
|||
name: 'name', // the name of the particular alarm field |
|||
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) |
|||
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". |
|||
}, |
|||
//... |
|||
] |
|||
} |
|||
``` |
|||
|
|||
- **alarms** - array of alarms (Array<[Alarm{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/alarm.models.ts#L89)>) received in scope of this subscription, using the following structure: |
|||
|
|||
```javascript |
|||
alarms = [ |
|||
{ // alarm |
|||
id: { // alarm id |
|||
entityType: "ALARM", |
|||
id: "943b8cd0-576a-11e7-824c-0b1cb331ec92" |
|||
}, |
|||
createdTime: 1498150092317, // Alarm created time (unix timestamp) |
|||
startTs: 1498150092316, // Alarm started time (unix timestamp) |
|||
endTs: 1498563899065, // Alarm end time (unix timestamp) |
|||
ackTs: 0, // Time of alarm acknowledgment (unix timestamp) |
|||
clearTs: 0, // Time of alarm clear (unix timestamp) |
|||
originator: { // Originator - id of entity produced this alarm |
|||
entityType: "ASSET", |
|||
id: "ceb16a30-4142-11e7-8b30-d5d66714ea5a" |
|||
}, |
|||
originatorName: "Originator Name", // Name of originator entity |
|||
type: "Temperature", // Type of the alarm |
|||
severity: "CRITICAL", // Severity of the alarm ("CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE") |
|||
status: "ACTIVE_UNACK", // Status of the alarm |
|||
// ("ACTIVE_UNACK" - active unacknowledged, |
|||
// "ACTIVE_ACK" - active acknowledged, |
|||
// "CLEARED_UNACK" - cleared unacknowledged, |
|||
// "CLEARED_ACK" - cleared acknowledged) |
|||
details: {} // Alarm details object derived from alarm details json. |
|||
} |
|||
] |
|||
``` |
|||
|
|||
For [RPC{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#rpc-control-widget) or [Static{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#static) widget types, subscription object is optional and does not contain necessary information. |
|||
@ -0,0 +1,14 @@ |
|||
#### Type parameters object |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
Object [WidgetTypeParameters{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L151) describing widget datasource parameters. It has the following properties: |
|||
|
|||
```javascript |
|||
return { |
|||
maxDatasources: -1, // Maximum allowed datasources for this widget, -1 - unlimited |
|||
maxDataKeys: -1, //Maximum allowed data keys for this widget, -1 - unlimited |
|||
dataKeysOptional: false //Whether this widget can be configured with datasources without data keys |
|||
} |
|||
``` |
|||
Loading…
Reference in new issue