Browse Source

Update help assets

pull/5496/head
Igor Kulikov 5 years ago
parent
commit
79588a7b70
  1. 7
      ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md
  2. 21
      ui-ngx/src/assets/help/en_US/rulenode/filter_node_script_fn.md
  3. 24
      ui-ngx/src/assets/help/en_US/rulenode/generator_node_script_fn.md
  4. 15
      ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md
  5. 26
      ui-ngx/src/assets/help/en_US/rulenode/transformation_node_script_fn.md
  6. 126
      ui-ngx/src/assets/help/en_US/widget/action/custom_action_fn.md
  7. 74
      ui-ngx/src/assets/help/en_US/widget/action/custom_pretty_action_fn.md
  8. 27
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_back_first_and_open_state.md
  9. 44
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_copy_access_token.md
  10. 33
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_delete_device_confirm.md
  11. 35
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_display_alert.md
  12. 34
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_open_state_save_parameters.md
  13. 18
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_return_previous_state.md
  14. 46
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_html.md
  15. 56
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_js.md
  16. 164
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_html.md
  17. 136
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_js.md
  18. 81
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_html.md
  19. 137
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_js.md
  20. 196
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_html.md
  21. 224
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_js.md
  22. 42
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_html.md
  23. 85
      ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_js.md
  24. 27
      ui-ngx/src/assets/help/en_US/widget/config/datakey_postprocess_fn.md
  25. 82
      ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_content_fn.md
  26. 10
      ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_style_fn.md
  27. 10
      ui-ngx/src/assets/help/en_US/widget/lib/entity/row_style_fn.md
  28. 11
      ui-ngx/src/assets/help/en_US/widget/lib/flot/ticks_formatter_fn.md
  29. 11
      ui-ngx/src/assets/help/en_US/widget/lib/flot/tooltip_value_format_fn.md
  30. BIN
      ui-ngx/src/assets/help/images/rulenode/examples/filter-node.png
  31. BIN
      ui-ngx/src/assets/help/images/rulenode/examples/switch-node.png

7
ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md

@ -1,8 +1,11 @@
<ul>
<li><b>msg:</b> <code>{[key: string]: any}</code> - is a Message payload key/value object.
</li>
<li><b>metadata:</b> <code>{[key: string]: string}</code> - is a Message metadata key/value object.
<li><b>metadata:</b> <code>{[key: string]: string}</code> - is a Message metadata key/value map, where both keys and values are strings.
</li>
<li><b>msgType:</b> <code>string</code> - is a string Message type. See <a href="https://github.com/thingsboard/thingsboard/blob/ea039008b148453dfa166cf92bc40b26e487e660/ui-ngx/src/app/shared/models/rule-node.models.ts#L338" target="_blank">MessageType</a> enum for common used values.
<li><b>msgType:</b> <code>string</code> - is a string containing Message type. See <a href="https://github.com/thingsboard/thingsboard/blob/ea039008b148453dfa166cf92bc40b26e487e660/ui-ngx/src/app/shared/models/rule-node.models.ts#L338" target="_blank">MessageType</a> enum for common used values.
</li>
</ul>
Enable 'debug mode' for your rule node to see the messages that arrive in near real-time.
See <a href="https://thingsboard.io/docs/user-guide/rule-engine-2-0/overview/#debugging" target="_blank">Debugging</a> for more information.

21
ui-ngx/src/assets/help/en_US/rulenode/filter_node_script_fn.md

@ -5,7 +5,7 @@
*function Filter(msg, metadata, msgType): boolean*
JavaScript function evaluating **true/false** condition on incoming Message.
JavaScript function defines a boolean expression based on the incoming Message and Metadata.
**Parameters:**
@ -13,19 +13,34 @@ JavaScript function evaluating **true/false** condition on incoming Message.
**Returns:**
Should return `boolean` value. If `true` - send Message via **True** chain, otherwise **False** chain is used.
Must return a `boolean` value. If `true` - routes Message to subsequent rule nodes that are related via **True** link,
otherwise sends Message to rule nodes related via **False** link.
Uses 'Failure' link in case of any failures to evaluate the expression.
<div class="divider"></div>
##### Examples
* Forward all messages with `temperature` value greater than `20` to the **True** chain and all other messages to the **False** chain:
* Forward all messages with `temperature` value greater than `20` to the **True** link and all other messages to the **False** link.
Assumes that incoming messages always contain the 'temperature' field:
```javascript
return msg.temperature > 20;
{:copy-code}
```
Example of the rule chain configuration:
![image](${helpBaseUrl}/help/images/rulenode/examples/filter-node.png)
* Same as above, but checks that the message has 'temperature' field to **avoid failures** on unexpected messages:
```javascript
return typeof msg.temperature !== 'undefined' && msg.temperature > 20;
{:copy-code}
```
* Forward all messages with type `ATTRIBUTES_UPDATED` to the **True** chain and all other messages to the **False** chain:
```javascript

24
ui-ngx/src/assets/help/en_US/rulenode/generator_node_script_fn.md

@ -5,7 +5,7 @@
*function Generate(prevMsg, prevMetadata, prevMsgType): {msg: object, metadata: object, msgType: string}*
JavaScript function generating new message using previous Message payload, Metadata and Message type as input arguments.
JavaScript function generating new Message using previous Message payload, Metadata and Message type as input arguments.
**Parameters:**
@ -24,13 +24,13 @@ Should return the object with the following structure:
```javascript
{
msg?: {[key: string]: any},
metadata?: {[key: string]: string},
msgType?: string
msg: {[key: string]: any},
metadata: {[key: string]: string},
msgType: string
}
```
All fields in resulting object are optional and will be taken from previously generated Message if not specified.
All fields in resulting object are mandatory.
<div class="divider"></div>
@ -39,14 +39,11 @@ All fields in resulting object are optional and will be taken from previously ge
* Generate message of type `POST_TELEMETRY_REQUEST` with random `temperature` value from `18` to `32`:
```javascript
var temperature = 18 + Math.random() * 14;
var temperature = 18 + Math.random() * (32 - 18);
// Round to at most 2 decimal places (optional)
temperature = Math.round( temperature * 100 ) / 100;
var msg = { temperature: temperature };
var metadata = {};
var msgType = "POST_TELEMETRY_REQUEST";
return { msg: msg, metadata: metadata, msgType: msgType };
return { msg: msg, metadata: {}, msgType: "POST_TELEMETRY_REQUEST" };
{:copy-code}
```
@ -62,9 +59,7 @@ and <strong>metadata</strong> with field <code>data</code> having value <code>40
```javascript
var msg = { temp: 42, humidity: 77 };
var metadata = { data: 40 };
var msgType = "POST_TELEMETRY_REQUEST";
return { msg: msg, metadata: metadata, msgType: msgType };
return { msg: msg, metadata: metadata, msgType: "POST_TELEMETRY_REQUEST" };
{:copy-code}
```
@ -108,9 +103,8 @@ if (isDecrement === 'true') {
var msg = { temperature: temperature };
var metadata = { isDecrement: isDecrement };
var msgType = "POST_TELEMETRY_REQUEST";
return { msg: msg, metadata: metadata, msgType: msgType };
return { msg: msg, metadata: metadata, msgType: "POST_TELEMETRY_REQUEST" };
{:copy-code}
```

15
ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md

@ -5,7 +5,7 @@
*function Switch(msg, metadata, msgType): string[]*
JavaScript function computing **an array of next Relation names** for incoming Message.
JavaScript function computing **an array of Link names** to forward the incoming Message.
**Parameters:**
@ -13,8 +13,9 @@ JavaScript function computing **an array of next Relation names** for incoming M
**Returns:**
Should return an array of `string` values presenting **next Relation names** where Message should be routed.<br>
If returned array is empty - message will not be routed to any Node and discarded.
Should return an array of `string` values presenting **link names** that the Rule Engine should use to further route the incoming Message.<br>
If the result is an empty array - message will not be routed to any Node and will be immediately
<a href="https://thingsboard.io/docs/user-guide/rule-engine-2-0/overview/#message-processing-result" target="_blank">acknowledged</a>.
<div class="divider"></div>
@ -24,7 +25,7 @@ If returned array is empty - message will not be routed to any Node and discarde
<li>
Forward all messages with <code>temperature</code> value greater than <code>30</code> to the <strong>'High temperature'</strong> chain,<br>
with <code>temperature</code> value lower than <code>20</code> to the <strong>'Low temperature'</strong> chain and all other messages<br>
to the <strong>'Normal temperature'</strong> chain:
to the <strong>'Other'</strong> chain:
</li>
</ul>
@ -34,11 +35,15 @@ if (msg.temperature > 30) {
} else if (msg.temperature < 20) {
return ['Low temperature'];
} else {
return ['Normal temperature'];
return ['Other'];
}
{:copy-code}
```
Example of the rule chain configuration:
![image](${helpBaseUrl}/help/images/rulenode/examples/switch-node.png)
<ul>
<li>
For messages with type <code>POST_TELEMETRY_REQUEST</code>:

26
ui-ngx/src/assets/help/en_US/rulenode/transformation_node_script_fn.md

@ -5,7 +5,7 @@
*function Transform(msg, metadata, msgType): {msg: object, metadata: object, msgType: string}*
JavaScript function transforming input Message payload, Metadata or Message type.
The JavaScript function to transform input Message payload, Metadata and/or Message type to the output message.
**Parameters:**
@ -29,11 +29,29 @@ All fields in resulting object are optional and will be taken from original mess
##### Examples
* Change message type to `CUSTOM_REQUEST`:
* Add sum of two fields ('a' and 'b') as a new field ('sum') of existing message:
```javascript
return { msgType: 'CUSTOM_REQUEST' };
{:copy-code}
if(typeof msg.a !== "undefined" && typeof msg.b !== "undefined"){
msg.sum = msg.a + msg.b;
}
return {msg: msg};
```
* Transform value of the 'temperature' field from °F to °C:
```javascript
msg.temperature = (msg.temperature - 32) * 5 / 9;
return {msg: msg};
```
* Replace the incoming message with the new message that contains only one field - count of properties in the original message:
```javascript
var newMsg = {
count: Object.keys(msg).length
};
return {msg: newMsg};
```
<ul>

126
ui-ngx/src/assets/help/en_US/widget/action/custom_action_fn.md

@ -17,67 +17,65 @@ A JavaScript function performing custom action.
##### Examples
* Display alert dialog with entity information:
```javascript
{:code-style="max-height: 300px;"}
var title;
var content;
if (entityName) {
title = entityName + ' details';
content = '<b>Entity name</b>: ' + entityName;
if (additionalParams && additionalParams.entity) {
var entity = additionalParams.entity;
if (entity.id) {
content += '<br><b>Entity type</b>: ' + entity.id.entityType;
}
if (!isNaN(entity.temperature) && entity.temperature !== '') {
content += '<br><b>Temperature</b>: ' + entity.temperature + ' °C';
}
}
} else {
title = 'No entity information available';
content = '<b>No entity information available</b>';
}
showAlertDialog(title, content);
function showAlertDialog(title, content) {
setTimeout(function() {
widgetContext.dialogs.alert(title, content).subscribe();
}, 100);
}
{:copy-code}
```
* Delete device after confirmation:
```javascript
{:code-style="max-height: 300px;"}
var $injector = widgetContext.$scope.$injector;
var dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));
var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
openDeleteDeviceDialog();
function openDeleteDeviceDialog() {
var title = 'Are you sure you want to delete the device ' + entityName + '?';
var content = 'Be careful, after the confirmation, the device and all related data will become unrecoverable!';
dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(
function(result) {
if (result) {
deleteDevice();
}
}
);
}
function deleteDevice() {
deviceService.deleteDevice(entityId.id).subscribe(
function() {
widgetContext.updateAliases();
}
);
}
{:copy-code}
```
<br>
<div style="padding-left: 32px;"
tb-help-popup="widget/action/examples_custom_action/custom_action_display_alert"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="Display alert dialog with entity information">
</div>
<br>
<div style="padding-left: 32px;"
tb-help-popup="widget/action/examples_custom_action/custom_action_delete_device_confirm"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="Delete device after confirmation">
</div>
<br>
<div style="padding-left: 32px;"
tb-help-popup="widget/action/examples_custom_action/custom_action_return_previous_state"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="Return to the previous state">
</div>
<br>
<div style="padding-left: 32px;"
tb-help-popup="widget/action/examples_custom_action/custom_action_open_state_save_parameters"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="Open state conditionally with saving particular state parameters">
</div>
<br>
<div style="padding-left: 32px;"
tb-help-popup="widget/action/examples_custom_action/custom_action_back_first_and_open_state"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="Go back to the first state, after this go to the target state">
</div>
<br>
<div style="padding-left: 32px;"
tb-help-popup="widget/action/examples_custom_action/custom_action_copy_access_token"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="Copy device access token to buffer">
</div>
<br>
<br>

74
ui-ngx/src/assets/help/en_US/widget/action/custom_pretty_action_fn.md

@ -42,7 +42,7 @@ A JavaScript function performing custom action with defined HTML template to ren
<br>
<div style="padding-left: 64px;"
tb-help-popup="widget/action/examples/custom_pretty_create_dialog_js"
tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_create_dialog_js"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
@ -52,7 +52,7 @@ A JavaScript function performing custom action with defined HTML template to ren
<br>
<div style="padding-left: 64px;"
tb-help-popup="widget/action/examples/custom_pretty_create_dialog_html"
tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_create_dialog_html"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
@ -64,7 +64,7 @@ A JavaScript function performing custom action with defined HTML template to ren
<br>
<div style="padding-left: 64px;"
tb-help-popup="widget/action/examples/custom_pretty_edit_dialog_js"
tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_edit_dialog_js"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
@ -74,7 +74,73 @@ A JavaScript function performing custom action with defined HTML template to ren
<br>
<div style="padding-left: 64px;"
tb-help-popup="widget/action/examples/custom_pretty_edit_dialog_html"
tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_edit_dialog_html"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="HTML code">
</div>
###### Display dialog to created new user
<br>
<div style="padding-left: 64px;"
tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_create_user_js"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="JavaScript function">
</div>
<br>
<div style="padding-left: 64px;"
tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_create_user_html"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="HTML code">
</div>
###### Display dialog to add/edit image in entity attribute
<br>
<div style="padding-left: 64px;"
tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_edit_image_js"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="JavaScript function">
</div>
<br>
<div style="padding-left: 64px;"
tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_edit_image_html"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="HTML code">
</div>
###### Display dialog to clone device
<br>
<div style="padding-left: 64px;"
tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_clone_device_js"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"
trigger-text="JavaScript function">
</div>
<br>
<div style="padding-left: 64px;"
tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_clone_device_html"
tb-help-popup-placement="top"
[tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}"
trigger-style="font-size: 16px;"

27
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_back_first_and_open_state.md

@ -0,0 +1,27 @@
#### Function go back to the first state, after this go to the target state
```javascript
{:code-style="max-height: 400px;"}
var stateIndex = widgetContext.stateController.getStateIndex();
while (stateIndex > 0) {
stateIndex -= 1;
backToPrevState(stateIndex);
}
openDashboardState('devices');
function backToPrevState(stateIndex) {
widgetContext.stateController.navigatePrevState(stateIndex);
}
function openDashboardState(statedId) {
var currentState = widgetContext.stateController.getStateId();
if (currentState !== statedId) {
var params = {};
widgetContext.stateController.updateState(statedId, params, false);
}
}
{:copy-code}
```
<br>
<br>

44
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_copy_access_token.md

@ -0,0 +1,44 @@
#### Function copy device access token to buffer
```javascript
{:code-style="max-height: 400px;"}
var $injector = widgetContext.$scope.$injector;
var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
var $translate = $injector.get(widgetContext.servicesMap.get('translate'));
var $scope = widgetContext.$scope;
if (entityId.id && entityId.entityType === 'DEVICE') {
deviceService.getDeviceCredentials(entityId.id, true).subscribe(
(deviceCredentials) => {
var credentialsId = deviceCredentials.credentialsId;
if (copyToClipboard(credentialsId)) {
$scope.showSuccessToast($translate.instant('device.accessTokenCopiedMessage'), 750, "top", "left");
}
}
);
}
function copyToClipboard(text) {
if (window.clipboardData && window.clipboardData.setData) {
return window.clipboardData.setData("Text", text);
}
else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
var textarea = document.createElement("textarea");
textarea.textContent = text;
textarea.style.position = "fixed";
document.body.appendChild(textarea);
textarea.select();
try {
return document.execCommand("copy");
}
catch (ex) {
console.warn("Copy to clipboard failed.", ex);
return false;
}
document.body.removeChild(textarea);
}
}
{:copy-code}
```
<br>
<br>

33
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_delete_device_confirm.md

@ -0,0 +1,33 @@
#### Function delete device after confirmation
```javascript
var $injector = widgetContext.$scope.$injector;
var dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));
var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
openDeleteDeviceDialog();
function openDeleteDeviceDialog() {
var title = 'Are you sure you want to delete the device ' + entityName + '?';
var content = 'Be careful, after the confirmation, the device and all related data will become unrecoverable!';
dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(
function(result) {
if (result) {
deleteDevice();
}
}
);
}
function deleteDevice() {
deviceService.deleteDevice(entityId.id).subscribe(
function() {
widgetContext.updateAliases();
}
);
}
{:copy-code}
```
<br>
<br>

35
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_display_alert.md

@ -0,0 +1,35 @@
#### Function display alert dialog with entity information
```javascript
{:code-style="max-height: 400px;"}
var title;
var content;
if (entityName) {
title = entityName + ' details';
content = '<b>Entity name</b>: ' + entityName;
if (additionalParams && additionalParams.entity) {
var entity = additionalParams.entity;
if (entity.id) {
content += '<br><b>Entity type</b>: ' + entity.id.entityType;
}
if (!isNaN(entity.temperature) && entity.temperature !== '') {
content += '<br><b>Temperature</b>: ' + entity.temperature + ' °C';
}
}
} else {
title = 'No entity information available';
content = '<b>No entity information available</b>';
}
showAlertDialog(title, content);
function showAlertDialog(title, content) {
setTimeout(function() {
widgetContext.dialogs.alert(title, content).subscribe();
}, 100);
}
{:copy-code}
```
<br>
<br>

34
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_open_state_save_parameters.md

@ -0,0 +1,34 @@
#### Function open state conditionally with saving particular state parameters
```javascript
{:code-style="max-height: 400px;"}
var entitySubType;
var $injector = widgetContext.$scope.$injector;
$injector.get(widgetContext.servicesMap.get('entityService')).getEntity(entityId.entityType, entityId.id)
.subscribe(function(data) {
entitySubType = data.type;
if (entitySubType == 'energy meter') {
openDashboardStates('energy_meter_details_view');
} else if (entitySubType == 'thermometer') {
openDashboardStates('thermometer_details_view');
}
});
function openDashboardStates(statedId) {
var stateParams = widgetContext.stateController.getStateParams();
var params = {
entityId: entityId,
entityName: entityName
};
if (stateParams.city) {
params.city = stateParams.city;
}
widgetContext.stateController.openState(statedId, params, false);
}
{:copy-code}
```
<br>
<br>

18
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_return_previous_state.md

@ -0,0 +1,18 @@
#### Function return to the previous state
```javascript
{:code-style="max-height: 400px;"}
let stateIndex = widgetContext.stateController.getStateIndex();
if (stateIndex > 0) {
stateIndex -= 1;
backToPrevState(stateIndex);
}
function backToPrevState(stateIndex) {
widgetContext.stateController.navigatePrevState(stateIndex);
}
{:copy-code}
```
<br>
<br>

46
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_html.md

@ -0,0 +1,46 @@
#### HTML template of dialog to clone device
```html
{:code-style="max-height: 400px;"}
<form [formGroup]="cloneDeviceFormGroup" (ngSubmit)="save()" style="min-width:320px;">
<mat-toolbar fxLayout="row" color="primary">
<h2>Clone device: {{ deviceName }}</h2>
<span fxFlex></span>
<button mat-icon-button (click)="cancel()"
type="button">
<mat-icon class="material-icons">close
</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate"
*ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label>Clone device name</mat-label>
<input matInput formControlName="cloneName" required>
<mat-error *ngIf="cloneDeviceFormGroup.get('cloneName').hasError('required')">
Clone device name is required
</mat-error>
</mat-form-field>
</div>
<div mat-dialog-actions fxLayout="row"
fxLayoutAlign="end center">
<button mat-button color="primary" type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
Cancel
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || cloneDeviceFormGroup.invalid || !cloneDeviceFormGroup.dirty">
Save
</button>
</div>
</form>
{:copy-code}
```
<br>
<br>

56
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_js.md

@ -0,0 +1,56 @@
#### Function displaying dialog to clone device
```javascript
{:code-style="max-height: 400px;"}
const $injector = widgetContext.$scope.$injector;
const customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
const attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
const deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
const rxjs = widgetContext.rxjs;
openCloneDeviceDialog();
function openCloneDeviceDialog() {
customDialog.customDialog(htmlTemplate, CloneDeviceDialogController).subscribe();
}
function CloneDeviceDialogController(instance) {
let vm = instance;
vm.deviceName = entityName;
vm.cloneDeviceFormGroup = vm.fb.group({
cloneName: ['', [vm.validators.required]]
});
vm.save = function() {
deviceService.getDevice(entityId.id).pipe(
rxjs.mergeMap((origDevice) => {
let cloneDevice = {
name: vm.cloneDeviceFormGroup.get('cloneName').value,
type: origDevice.type
};
return deviceService.saveDevice(cloneDevice).pipe(
rxjs.mergeMap((newDevice) => {
return attributeService.getEntityAttributes(origDevice.id, 'SERVER_SCOPE').pipe(
rxjs.mergeMap((origAttributes) => {
return attributeService.saveEntityAttributes(newDevice.id, 'SERVER_SCOPE', origAttributes);
})
);
})
);
})
).subscribe(() => {
widgetContext.updateAliases();
vm.dialogRef.close(null);
});
};
vm.cancel = function() {
vm.dialogRef.close(null);
};
}
{:copy-code}
```
<br>
<br>

164
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_html.md

@ -0,0 +1,164 @@
#### HTML template of dialog to create a device or an asset
```html
{:code-style="max-height: 400px;"}
<form #addEntityForm="ngForm" [formGroup]="addEntityFormGroup"
(ngSubmit)="save()" class="add-entity-form">
<mat-toolbar fxLayout="row" color="primary">
<h2>Add entity</h2>
<span fxFlex></span>
<button mat-icon-button (click)="cancel()" type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Entity Name</mat-label>
<input matInput formControlName="entityName" required>
<mat-error *ngIf="addEntityFormGroup.get('entityName').hasError('required')">
Entity name is required.
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Entity Label</mat-label>
<input matInput formControlName="entityLabel" >
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<tb-entity-type-select
class="mat-block"
formControlName="entityType"
[showLabel]="true"
[allowedEntityTypes]="allowedEntityTypes"
></tb-entity-type-select>
<tb-entity-subtype-autocomplete
fxFlex *ngIf="addEntityFormGroup.get('entityType').value == 'ASSET'"
class="mat-block"
formControlName="type"
[required]="true"
[entityType]="'ASSET'"
></tb-entity-subtype-autocomplete>
<tb-entity-subtype-autocomplete
fxFlex *ngIf="addEntityFormGroup.get('entityType').value != 'ASSET'"
class="mat-block"
formControlName="type"
[required]="true"
[entityType]="'DEVICE'"
></tb-entity-subtype-autocomplete>
</div>
<div formGroupName="attributes" fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Latitude</mat-label>
<input type="number" step="any" matInput formControlName="latitude">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Longitude</mat-label>
<input type="number" step="any" matInput formControlName="longitude">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Address</mat-label>
<input matInput formControlName="address">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Owner</mat-label>
<input matInput formControlName="owner">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Integer Value</mat-label>
<input type="number" step="1" matInput formControlName="number">
<mat-error *ngIf="addEntityFormGroup.get('attributes.number').hasError('pattern')">
Invalid integer value.
</mat-error>
</mat-form-field>
<div class="boolean-value-input" fxLayout="column" fxLayoutAlign="center start" fxFlex>
<label class="checkbox-label">Boolean Value</label>
<mat-checkbox formControlName="booleanValue" style="margin-bottom: 40px;">
</mat-checkbox>
</div>
</div>
</div>
<div class="relations-list">
<div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div>
<div class="body" [fxShow]="relations().length">
<div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="relations" *ngFor="let relation of relations().controls; let i = index;">
<div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;">
<div fxFlex fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field class="mat-block" style="min-width: 100px;">
<mat-label>Direction</mat-label>
<mat-select formControlName="direction" name="direction">
<mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value">
</mat-option>
</mat-select>
<mat-error *ngIf="relation.get('direction').hasError('required')">
Relation direction is required.
</mat-error>
</mat-form-field>
<tb-relation-type-autocomplete
fxFlex class="mat-block"
formControlName="relationType"
[required]="true">
</tb-relation-type-autocomplete>
</div>
<div fxLayout="row" fxLayout.xs="column">
<tb-entity-select
fxFlex class="mat-block"
[required]="true"
formControlName="relatedEntity">
</tb-entity-select>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="center center">
<button mat-icon-button color="primary"
aria-label="Remove"
type="button"
(click)="removeRelation(i)"
matTooltip="Remove relation"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</div>
</div>
<div>
<button mat-raised-button color="primary"
type="button"
(click)="addRelation()"
matTooltip="Add Relation"
matTooltipPosition="above">
Add
</button>
</div>
</div>
</div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
Cancel
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty">
Create
</button>
</div>
</form>
{:copy-code}
```
<br>
<br>

136
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_js.md

@ -0,0 +1,136 @@
#### Function displaying dialog to create a device or an asset
```javascript
{:code-style="max-height: 400px;"}
let $injector = widgetContext.$scope.$injector;
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
let entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));
openAddEntityDialog();
function openAddEntityDialog() {
customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();
}
function AddEntityDialogController(instance) {
let vm = instance;
vm.allowedEntityTypes = ['ASSET', 'DEVICE'];
vm.entitySearchDirection = {
from: "FROM",
to: "TO"
}
vm.addEntityFormGroup = vm.fb.group({
entityName: ['', [vm.validators.required]],
entityType: ['DEVICE'],
entityLabel: [null],
type: ['', [vm.validators.required]],
attributes: vm.fb.group({
latitude: [null],
longitude: [null],
address: [null],
owner: [null],
number: [null, [vm.validators.pattern(/^-?[0-9]+$/)]],
booleanValue: [null]
}),
relations: vm.fb.array([])
});
vm.cancel = function () {
vm.dialogRef.close(null);
};
vm.relations = function () {
return vm.addEntityFormGroup.get('relations');
};
vm.addRelation = function () {
vm.relations().push(vm.fb.group({
relatedEntity: [null, [vm.validators.required]],
relationType: [null, [vm.validators.required]],
direction: [null, [vm.validators.required]]
}));
};
vm.removeRelation = function (index) {
vm.relations().removeAt(index);
vm.relations().markAsDirty();
};
vm.save = function () {
vm.addEntityFormGroup.markAsPristine();
saveEntityObservable().subscribe(
function (entity) {
widgetContext.rxjs.forkJoin([
saveAttributes(entity.id),
saveRelations(entity.id)
]).subscribe(
function () {
widgetContext.updateAliases();
vm.dialogRef.close(null);
}
);
}
);
};
function saveEntityObservable() {
const formValues = vm.addEntityFormGroup.value;
let entity = {
name: formValues.entityName,
type: formValues.type,
label: formValues.entityLabel
};
if (formValues.entityType == 'ASSET') {
return assetService.saveAsset(entity);
} else if (formValues.entityType == 'DEVICE') {
return deviceService.saveDevice(entity);
}
}
function saveAttributes(entityId) {
let attributes = vm.addEntityFormGroup.get('attributes').value;
let attributesArray = [];
for (let key in attributes) {
if (attributes[key] !== null) {
attributesArray.push({key: key, value: attributes[key]});
}
}
if (attributesArray.length > 0) {
return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray);
}
return widgetContext.rxjs.of([]);
}
function saveRelations(entityId) {
let relations = vm.addEntityFormGroup.get('relations').value;
let tasks = [];
for (let i = 0; i < relations.length; i++) {
let relation = {
type: relations[i].relationType,
typeGroup: 'COMMON'
};
if (relations[i].direction == 'FROM') {
relation.to = relations[i].relatedEntity;
relation.from = entityId;
} else {
relation.to = entityId;
relation.from = relations[i].relatedEntity;
}
tasks.push(entityRelationService.saveRelation(relation));
}
if (tasks.length > 0) {
return widgetContext.rxjs.forkJoin(tasks);
}
return widgetContext.rxjs.of([]);
}
}
{:copy-code}
```
<br>
<br>

81
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_html.md

@ -0,0 +1,81 @@
#### HTML template of dialog to create new user
```html
{:code-style="max-height: 400px;"}
<form [formGroup]="addEntityFormGroup" (ngSubmit)="save()" style="min-width:480px;">
<mat-toolbar fxLayout="row" color="primary">
<h2>Add new User</h2>
<span fxFlex></span>
<button mat-icon-button (click)="cancel()" type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate"
*ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)">
</div>
<div mat-dialog-content fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px"
fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Email</mat-label>
<input matInput formControlName="email" required
ng-pattern='/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\_\-0-9]+\.)+[a-zA-Z]{2,}))$/'>
<mat-error *ngIf="addEntityFormGroup.get('email').hasError('required')">
Email is required
</mat-error>
<mat-error *ngIf="addEntityFormGroup.get('email').hasError('pattern')">
Invalid email format
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px"
fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>First Name</mat-label>
<input matInput
formControlName="firstName">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px"
fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Last Name</mat-label>
<input matInput
formControlName="lastName">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px"
fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>User activation method</mat-label>
<mat-select matInput formControlName="userActivationMethod">
<mat-option *ngFor="let activationMethod of activationMethods" [value]="activationMethod.value">
{{activationMethod.name}}
</mat-option>
</mat-select>
<mat-error *ngIf="addEntityFormGroup.get('userActivationMethod').hasError('required')">Please choose activation method</mat-error>
<mat-hint>e.g. Send activation email</mat-hint>
</mat-form-field>
</div>
</div>
<div mat-dialog-actions fxLayout="row"
fxLayoutAlign="end center">
<button mat-button color="primary" type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
Cancel
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || addEntityFormGroup.invalid || !addEntityFormGroup.dirty">
Create
</button>
</div>
</form>
{:copy-code}
```
<br>
<br>

137
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_js.md

@ -0,0 +1,137 @@
#### Function displaying dialog to create new user
```javascript
{:code-style="max-height: 400px;"}
const $injector = widgetContext.$scope.$injector;
const customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
const userService = $injector.get(widgetContext.servicesMap.get('userService'));
const $scope = widgetContext.$scope;
const rxjs = widgetContext.rxjs;
openAddUserDialog();
function openAddUserDialog() {
customDialog.customDialog(htmlTemplate, AddUserDialogController).subscribe();
}
function AddUserDialogController(instance) {
let vm = instance;
vm.currentUser = widgetContext.currentUser;
vm.addEntityFormGroup = vm.fb.group({
email: ['', [vm.validators.required, vm.validators.pattern(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\_\-0-9]+\.)+[a-zA-Z]{2,}))$/)]],
firstName: [''],
lastName: ['', ],
userActivationMethod: ['', [vm.validators.required]]
});
vm.activationMethods = [
{
value: 'displayActivationLink',
name: 'Display activation link'
},
{
value: 'sendActivationMail',
name: 'Send activation email'
}
];
vm.cancel = function() {
vm.dialogRef.close(null);
};
vm.save = function() {
let formObj = vm.addEntityFormGroup.getRawValue();
let attributes = [];
let sendActivationMail = false;
let newUser = {
email: formObj.email,
firstName: formObj.firstName,
lastName: formObj.lastName,
authority: 'TENANT_ADMIN'
};
if (formObj.userActivationMethod === 'sendActivationMail') {
sendActivationMail = true;
}
userService.saveUser(newUser, sendActivationMail).pipe(
rxjs.mergeMap((user) => {
let activationObs;
if (sendActivationMail) {
activationObs = rxjs.of(null);
} else {
activationObs = userService.getActivationLink(user.id.id);
}
return activationObs.pipe(
rxjs.mergeMap((activationLink) => {
return activationLink ? customDialog.customDialog(activationLinkDialogTemplate, ActivationLinkDialogController, {"activationLink": activationLink}) : rxjs.of(null);
})
);
})
).subscribe(() => {
vm.dialogRef.close(null);
});
};
}
function ActivationLinkDialogController(instance) {
let vm = instance;
vm.activationLink = vm.data.activationLink;
vm.onActivationLinkCopied = () => {
$scope.showSuccessToast("User activation link has been copied to clipboard", 1200, "bottom", "left", "activationLinkDialogContent");
};
vm.close = () => {
vm.dialogRef.close(null);
};
}
let activationLinkDialogTemplate = `<form style="min-width: 400px; position: relative;">
<mat-toolbar color="primary">
<h2 translate>user.activation-link</h2>
<span fxFlex></span>
<button mat-button mat-icon-button
(click)="close()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content tb-toast toastTarget="activationLinkDialogContent">
<div class="mat-content" fxLayout="column">
<span [innerHTML]="'user.activation-link-text' | translate: {activationLink: activationLink}"></span>
<div fxLayout="row" fxLayoutAlign="start center">
<pre class="tb-highlight" fxFlex><code>{{ activationLink }}</code></pre>
<button mat-icon-button
color="primary"
ngxClipboard
cbContent="{{ activationLink }}"
(cbOnSuccess)="onActivationLinkCopied()"
matTooltip="{{ 'user.copy-activation-link' | translate }}"
matTooltipPosition="above">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
</button>
</div>
</div>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
cdkFocusInitial
[disabled]="(isLoading$ | async)"
(click)="close()">
{{ 'action.ok' | translate }}
</button>
</div>
</form>`;
{:copy-code}
```
<br>
<br>

196
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_html.md

@ -0,0 +1,196 @@
#### HTML template of dialog to edit a device or an asset
```html
{:code-style="max-height: 400px;"}
<form #editEntityForm="ngForm" [formGroup]="editEntityFormGroup"
(ngSubmit)="save()" class="edit-entity-form">
<mat-toolbar fxLayout="row" color="primary">
<h2>Edit </h2>
<span fxFlex></span>
<button mat-icon-button (click)="cancel()" type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Entity Name</mat-label>
<input matInput formControlName="entityName" required readonly="">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Entity Label</mat-label>
<input matInput formControlName="entityLabel">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Entity Type</mat-label>
<input matInput formControlName="entityType" readonly>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Type</mat-label>
<input matInput formControlName="type" readonly>
</mat-form-field>
</div>
<div formGroupName="attributes" fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Latitude</mat-label>
<input type="number" step="any" matInput formControlName="latitude">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Longitude</mat-label>
<input type="number" step="any" matInput formControlName="longitude">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Address</mat-label>
<input matInput formControlName="address">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Owner</mat-label>
<input matInput formControlName="owner">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Integer Value</mat-label>
<input type="number" step="1" matInput formControlName="number">
<mat-error *ngIf="editEntityFormGroup.get('attributes.number').hasError('pattern')">
Invalid integer value.
</mat-error>
</mat-form-field>
<div class="boolean-value-input" fxLayout="column" fxLayoutAlign="center start" fxFlex>
<label class="checkbox-label">Boolean Value</label>
<mat-checkbox formControlName="booleanValue" style="margin-bottom: 40px;">
</mat-checkbox>
</div>
</div>
</div>
<div class="relations-list old-relations">
<div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div>
<div class="body" [fxShow]="oldRelations().length">
<div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="oldRelations"
*ngFor="let relation of oldRelations().controls; let i = index;">
<div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;">
<div fxFlex fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field class="mat-block" style="min-width: 100px;">
<mat-label>Direction</mat-label>
<mat-select formControlName="direction" name="direction">
<mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value">
</mat-option>
</mat-select>
<mat-error *ngIf="relation.get('direction').hasError('required')">
Relation direction is required.
</mat-error>
</mat-form-field>
<tb-relation-type-autocomplete
fxFlex class="mat-block"
formControlName="relationType"
required="true">
</tb-relation-type-autocomplete>
</div>
<div fxLayout="row" fxLayout.xs="column">
<tb-entity-select
fxFlex class="mat-block"
required="true"
formControlName="relatedEntity">
</tb-entity-select>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="center center">
<button mat-icon-button color="primary"
aria-label="Remove"
type="button"
(click)="removeOldRelation(i)"
matTooltip="Remove relation"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="relations-list">
<div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">New Relations</div>
<div class="body" [fxShow]="relations().length">
<div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="relations" *ngFor="let relation of relations().controls; let i = index;">
<div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;">
<div fxFlex fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field class="mat-block" style="min-width: 100px;">
<mat-label>Direction</mat-label>
<mat-select formControlName="direction" name="direction">
<mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value">
</mat-option>
</mat-select>
<mat-error *ngIf="relation.get('direction').hasError('required')">
Relation direction is required.
</mat-error>
</mat-form-field>
<tb-relation-type-autocomplete
fxFlex class="mat-block"
formControlName="relationType"
[required]="true">
</tb-relation-type-autocomplete>
</div>
<div fxLayout="row" fxLayout.xs="column">
<tb-entity-select
fxFlex class="mat-block"
[required]="true"
formControlName="relatedEntity">
</tb-entity-select>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="center center">
<button mat-icon-button color="primary"
aria-label="Remove"
type="button"
(click)="removeRelation(i)"
matTooltip="Remove relation"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</div>
</div>
<div>
<button mat-raised-button color="primary"
type="button"
(click)="addRelation()"
matTooltip="Add Relation"
matTooltipPosition="above">
Add
</button>
</div>
</div>
</div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
Cancel
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty">
Save
</button>
</div>
</form>
{:copy-code}
```
<br>
<br>

224
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_js.md

@ -0,0 +1,224 @@
#### Function displaying dialog to edit a device or an asset
```javascript
{:code-style="max-height: 400px;"}
let $injector = widgetContext.$scope.$injector;
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
let entityService = $injector.get(widgetContext.servicesMap.get('entityService'));
let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
let entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));
openEditEntityDialog();
function openEditEntityDialog() {
customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();
}
function EditEntityDialogController(instance) {
let vm = instance;
vm.entityName = entityName;
vm.entityType = entityId.entityType;
vm.entitySearchDirection = {
from: "FROM",
to: "TO"
};
vm.attributes = {};
vm.oldRelationsData = [];
vm.relationsToDelete = [];
vm.entity = {};
vm.editEntityFormGroup = vm.fb.group({
entityName: ['', [vm.validators.required]],
entityType: [null],
entityLabel: [null],
type: ['', [vm.validators.required]],
attributes: vm.fb.group({
latitude: [null],
longitude: [null],
address: [null],
owner: [null],
number: [null, [vm.validators.pattern(/^-?[0-9]+$/)]],
booleanValue: [false]
}),
oldRelations: vm.fb.array([]),
relations: vm.fb.array([])
});
getEntityInfo();
vm.cancel = function() {
vm.dialogRef.close(null);
};
vm.relations = function() {
return vm.editEntityFormGroup.get('relations');
};
vm.oldRelations = function() {
return vm.editEntityFormGroup.get('oldRelations');
};
vm.addRelation = function() {
vm.relations().push(vm.fb.group({
relatedEntity: [null, [vm.validators.required]],
relationType: [null, [vm.validators.required]],
direction: [null, [vm.validators.required]]
}));
};
function addOldRelation() {
vm.oldRelations().push(vm.fb.group({
relatedEntity: [{value: null, disabled: true}, [vm.validators.required]],
relationType: [{value: null, disabled: true}, [vm.validators.required]],
direction: [{value: null, disabled: true}, [vm.validators.required]]
}));
}
vm.removeRelation = function(index) {
vm.relations().removeAt(index);
vm.relations().markAsDirty();
};
vm.removeOldRelation = function(index) {
vm.oldRelations().removeAt(index);
vm.relationsToDelete.push(vm.oldRelationsData[index]);
vm.oldRelations().markAsDirty();
};
vm.save = function() {
vm.editEntityFormGroup.markAsPristine();
widgetContext.rxjs.forkJoin([
saveAttributes(entityId),
saveRelations(entityId),
saveEntity()
]).subscribe(
function () {
widgetContext.updateAliases();
vm.dialogRef.close(null);
}
);
};
function getEntityAttributes(attributes) {
for (var i = 0; i < attributes.length; i++) {
vm.attributes[attributes[i].key] = attributes[i].value;
}
}
function getEntityRelations(relations) {
let relationsFrom = relations[0];
let relationsTo = relations[1];
for (let i=0; i < relationsFrom.length; i++) {
let relation = {
direction: 'FROM',
relationType: relationsFrom[i].type,
relatedEntity: relationsFrom[i].to
};
vm.oldRelationsData.push(relation);
addOldRelation();
}
for (let i=0; i < relationsTo.length; i++) {
let relation = {
direction: 'TO',
relationType: relationsTo[i].type,
relatedEntity: relationsTo[i].from
};
vm.oldRelationsData.push(relation);
addOldRelation();
}
}
function getEntityInfo() {
widgetContext.rxjs.forkJoin([
entityRelationService.findInfoByFrom(entityId),
entityRelationService.findInfoByTo(entityId),
attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE'),
entityService.getEntity(entityId.entityType, entityId.id)
]).subscribe(
function (data) {
getEntityRelations(data.slice(0,2));
getEntityAttributes(data[2]);
vm.entity = data[3];
vm.editEntityFormGroup.patchValue({
entityName: vm.entity.name,
entityType: vm.entityType,
entityLabel: vm.entity.label,
type: vm.entity.type,
attributes: vm.attributes,
oldRelations: vm.oldRelationsData
}, {emitEvent: false});
}
);
}
function saveEntity() {
const formValues = vm.editEntityFormGroup.value;
if (vm.entity.label !== formValues.entityLabel){
vm.entity.label = formValues.entityLabel;
if (formValues.entityType == 'ASSET') {
return assetService.saveAsset(vm.entity);
} else if (formValues.entityType == 'DEVICE') {
return deviceService.saveDevice(vm.entity);
}
}
return widgetContext.rxjs.of([]);
}
function saveAttributes(entityId) {
let attributes = vm.editEntityFormGroup.get('attributes').value;
let attributesArray = [];
for (let key in attributes) {
if (attributes[key] !== vm.attributes[key]) {
attributesArray.push({key: key, value: attributes[key]});
}
}
if (attributesArray.length > 0) {
return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray);
}
return widgetContext.rxjs.of([]);
}
function saveRelations(entityId) {
let relations = vm.editEntityFormGroup.get('relations').value;
let tasks = [];
for(let i=0; i < relations.length; i++) {
let relation = {
type: relations[i].relationType,
typeGroup: 'COMMON'
};
if (relations[i].direction == 'FROM') {
relation.to = relations[i].relatedEntity;
relation.from = entityId;
} else {
relation.to = entityId;
relation.from = relations[i].relatedEntity;
}
tasks.push(entityRelationService.saveRelation(relation));
}
for (let i=0; i < vm.relationsToDelete.length; i++) {
let relation = {
type: vm.relationsToDelete[i].relationType
};
if (vm.relationsToDelete[i].direction == 'FROM') {
relation.to = vm.relationsToDelete[i].relatedEntity;
relation.from = entityId;
} else {
relation.to = entityId;
relation.from = vm.relationsToDelete[i].relatedEntity;
}
tasks.push(entityRelationService.deleteRelation(relation.from, relation.type, relation.to));
}
if (tasks.length > 0) {
return widgetContext.rxjs.forkJoin(tasks);
}
return widgetContext.rxjs.of([]);
}
}
{:copy-code}
```
<br>
<br>

42
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_html.md

@ -0,0 +1,42 @@
#### HTML template of dialog to add/edit image in entity attribute
```html
{:code-style="max-height: 400px;"}
<form [formGroup]="editEntity" (ngSubmit)="save()" class="edit-entity-form">
<mat-toolbar fxLayout="row" color="primary">
<h2>Edit {{entityName}} image</h2>
<span fxFlex></span>
<button mat-icon-button (click)="cancel()" type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="(isLoading$ | async) || loading">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async) && !loading"></div>
<div mat-dialog-content fxLayout="column">
<div formGroupName="attributes" fxLayout="column">
<tb-image-input
label="Entity image"
formControlName="image"
></tb-image-input>
</div>
</div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || editEntity.invalid || !editEntity.dirty">
Save
</button>
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
Cancel
</button>
</div>
</form>
{:copy-code}
```
<br>
<br>

85
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_js.md

@ -0,0 +1,85 @@
#### Function displaying dialog to add/edit image in entity attribute
```javascript
{:code-style="max-height: 400px;"}
let $injector = widgetContext.$scope.$injector;
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
let entityService = $injector.get(widgetContext.servicesMap.get('entityService'));
openAddEntityDialog();
function openAddEntityDialog() {
customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe(() => {});
}
function AddEntityDialogController(instance) {
let vm = instance;
vm.entityName = entityName;
vm.attributes = {};
vm.editEntity = vm.fb.group({
attributes: vm.fb.group({
image: [null]
})
});
getEntityInfo();
vm.cancel = function() {
vm.dialogRef.close(null);
};
vm.save = function() {
vm.loading = true;
saveAttributes(entityId).subscribe(
() => {
vm.dialogRef.close(null);
}, () =>{
vm.loading = false;
}
);
};
function getEntityAttributes(attributes) {
for (var i = 0; i < attributes.length; i++) {
vm.attributes[attributes[i].key] = attributes[i].value;
}
}
function getEntityInfo() {
vm.loading = true;
attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(
function (data) {
getEntityAttributes(data);
vm.editEntity.patchValue({
attributes: vm.attributes
}, {emitEvent: false});
vm.loading = false;
}
);
}
function saveAttributes(entityId) {
let attributes = vm.editEntity.get('attributes').value;
let attributesArray = [];
for (let key in attributes) {
if (attributes[key] !== vm.attributes[key]) {
attributesArray.push({key: key, value: attributes[key]});
}
}
if (attributesArray.length > 0) {
return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray);
}
return widgetContext.rxjs.of([]);
}
}
{:copy-code}
```
<br>
<br>

27
ui-ngx/src/assets/help/en_US/widget/config/datakey_postprocess_fn.md

@ -54,6 +54,33 @@ if (prevOrigValue) {
}
{:copy-code}
```
* Formatting data to time format
```javascript
if (value) {
return moment(value).format("DD/MM/YYYY HH:mm:ss");
}
return '';
{:copy-code}
```
* Creates line-breaks for 0 values, when used in line chart
```javascript
if (value === 0) {
return null;
} else {
return value;
}
{:copy-code}
```
* Display data point of the HTML value card under the condition
```javascript
return value ? '<div class="info"><b>Temperature: </b>'+value+' °C</div>' : '';
{:copy-code}
```
<br>
<br>

82
ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_content_fn.md

@ -57,5 +57,87 @@ return '<div style="border: 2px solid #0072ff; ' +
{:copy-code}
```
* Colored circles instead of boolean value:
```javascript
var color;
var active = value;
if (active == 'true') { // all key values here are strings
color = '#27AE60';
} else {
color = '#EB5757';
}
return '<span style="font-size: 18px; color: ' + color + '">&#11044;</span>';
{:copy-code}
```
* Decimal value format (1196 => 1,196.0):
```javascript
var value = value / 1;
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
return value ? numberWithCommas(value.toFixed(1)) : '';
{:copy-code}
```
* Show device status and icon this :
```javascript
{:code-style="max-height: 200px; max-width: 850px;"}
function getIcon(value) {
if (value == 'QUEUED') {
return '<mat-icon class="mat-icon material-icons mat-icon-no-color" data-mat-icon-type="font" style="color: #000;">' +
'<svg style="width:24px;height:24px" viewBox="0 0 24 24">' +
'<path fill="currentColor" d="M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z" />' +
'</svg>' +
'</mat-icon>';
}
if (value == 'UPDATED' ) {
return '<mat-icon class="mat-icon notranslate material-icons mat-icon-no-color" data-mat-icon-type="font" style="color: #000">' +
'update' +
'</mat-icon>';
}
return '';
}
function capitalize (s) {
if (typeof s !== 'string') return '';
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
}
var status = value;
return getIcon(status) + '<span style="vertical-align: super;padding-left: 8px;">' + capitalize(status) + '</span>';
{:copy-code}
```
* Display device attribute value on progress bar:
```javascript
{:code-style="max-height: 200px; max-width: 850px;"}
var progress = value;
if (value !== '') {
return `<mat-progress-bar style="height: 8px; padding-right: 30px" ` +
`role="progressbar" aria-valuemin="0" aria-valuemax="100" ` +
`tabindex="-1" mode="determinate" value="${progress}" ` +
`class="mat-progress-bar mat-primary" aria-valuenow="${progress}">` +
`<div aria-hidden="true">` +
`<svg width="100%" height="8" focusable="false" ` +
`class="mat-progress-bar-background mat-progress-bar-element">` +
`<defs>` +
`<pattern x="4" y="0" width="8" height="4" patternUnits="userSpaceOnUse" id="mat-progress-bar-0">` +
`<circle cx="2" cy="2" r="2"></circle>` +
`</pattern>` +
`</defs>` +
`<rect width="100%" height="100%" fill="url("/components/progress-bar/overview#mat-progress-bar-0")"></rect>` +
`</svg>` +
`<div class="mat-progress-bar-buffer mat-progress-bar-element"></div>` +
`<div class="mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element" style="transform: scale3d(${progress / 100}, 1, 1);"></div>` +
`<div class="mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element"></div>` +
`</div>` +
`</mat-progress-bar>`;
}
{:copy-code}
```
<br>
<br>

10
ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_style_fn.md

@ -29,6 +29,16 @@ Should return key/value object presenting style attributes.
##### Examples
* Set color and font-weight table cell content:
```javascript
return {
color:'rgb(0, 132, 214)',
fontWeight: 600
}
{:copy-code}
```
* Set color depending on device temperature value:
```javascript

10
ui-ngx/src/assets/help/en_US/widget/lib/entity/row_style_fn.md

@ -27,6 +27,16 @@ Should return key/value object presenting style attributes.
##### Examples
* Set color and font-weight table row:
```javascript
return {
color:'rgb(0, 132, 214)',
fontWeight: 600
}
{:copy-code}
```
* Set row background color depending on device type:
```javascript

11
ui-ngx/src/assets/help/en_US/widget/lib/flot/ticks_formatter_fn.md

@ -43,6 +43,17 @@ return '';
{:copy-code}
```
* Present ticks in decimal format (1196 => 1,196.0):
```javascript
var value = value / 1;
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
return value ? numberWithCommas(value.toFixed(1)) : '';
{:copy-code}
```
<ul>
<li>
To present axis ticks for true / false or 1 / 0 data.<br>

11
ui-ngx/src/assets/help/en_US/widget/lib/flot/tooltip_value_format_fn.md

@ -36,5 +36,16 @@ return value.toFixed(2) + ' A';
{:copy-code}
```
* Present the datapoint value in decimal format (1196 => 1,196.0):
```javascript
var value = value / 1;
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
return value ? numberWithCommas(value.toFixed(1)) : '';
{:copy-code}
```
<br>
<br>

BIN
ui-ngx/src/assets/help/images/rulenode/examples/filter-node.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
ui-ngx/src/assets/help/images/rulenode/examples/switch-node.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Loading…
Cancel
Save