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: |
#### Additional params object |
||||
<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>: |
<div class="divider"></div> |
||||
<ul> |
<br/> |
||||
<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 |
<b>additionalParams:</b> <code>{[key: string]: any}</code> |
||||
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> |
An optional key/value object holding additional entity parameters depending on widget type and action source: |
||||
</ul> |
|
||||
</li> |
<ul> |
||||
<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>: |
<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> |
<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 |
<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/shared/models/alarm.models.ts#L108" target="_blank">AlarmDataInfo</a> object |
<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 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. |
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> |
</li> |
||||
</ul> |
</ul> |
||||
</li> |
</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>: |
<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> |
<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 |
<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/modules/home/components/widget/lib/timeseries-table-widget.component.ts#L80" target="_blank">TimeseriesRow</a> object |
<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 <code>formattedTs</code> (a string value of formatted timestamp) and <br> timeseries values for each column declared in widget datasource configuration. |
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> |
</li> |
||||
</ul> |
</ul> |
||||
</li> |
</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>: |
<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> |
<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 |
<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/entities-hierarchy-widget.models.ts#L35" target="_blank">HierarchyNodeContext</a> object |
<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 |
||||
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. |
presenting <code>formattedTs</code> (a string value of formatted timestamp) and <br> timeseries values for each column declared in widget datasource configuration. |
||||
</li> |
</li> |
||||
</ul> |
</ul> |
||||
</li> |
</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>: |
<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> |
<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 |
<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/flot-widget.models.ts#L62" target="_blank">TbFlotPlotItem</a> object |
<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>series</code> field with information about datasource and <br> data key of clicked pie slice. |
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> |
</li> |
||||
</ul> |
</ul> |
||||
</li> |
</li> |
||||
<li><i>All other widgets</i> - does not provide <b>additionalParams</b> value. |
<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>: |
||||
</li> |
<ul> |
||||
</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> |
||||
|
<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