Showing
31 changed files
with
1705 additions
and
97 deletions
1 | <ul> | 1 | <ul> |
2 | <li><b>msg:</b> <code>{[key: string]: any}</code> - is a Message payload key/value object. | 2 | <li><b>msg:</b> <code>{[key: string]: any}</code> - is a Message payload key/value object. |
3 | </li> | 3 | </li> |
4 | - <li><b>metadata:</b> <code>{[key: string]: string}</code> - is a Message metadata key/value object. | 4 | + <li><b>metadata:</b> <code>{[key: string]: string}</code> - is a Message metadata key/value map, where both keys and values are strings. |
5 | </li> | 5 | </li> |
6 | - <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. | 6 | + <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. |
7 | </li> | 7 | </li> |
8 | </ul> | 8 | </ul> |
9 | + | ||
10 | +Enable 'debug mode' for your rule node to see the messages that arrive in near real-time. | ||
11 | +See <a href="https://thingsboard.io/docs/user-guide/rule-engine-2-0/overview/#debugging" target="_blank">Debugging</a> for more information. |
@@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
5 | 5 | ||
6 | *function Filter(msg, metadata, msgType): boolean* | 6 | *function Filter(msg, metadata, msgType): boolean* |
7 | 7 | ||
8 | -JavaScript function evaluating **true/false** condition on incoming Message. | 8 | +JavaScript function defines a boolean expression based on the incoming Message and Metadata. |
9 | 9 | ||
10 | **Parameters:** | 10 | **Parameters:** |
11 | 11 | ||
@@ -13,19 +13,34 @@ JavaScript function evaluating **true/false** condition on incoming Message. | @@ -13,19 +13,34 @@ JavaScript function evaluating **true/false** condition on incoming Message. | ||
13 | 13 | ||
14 | **Returns:** | 14 | **Returns:** |
15 | 15 | ||
16 | -Should return `boolean` value. If `true` - send Message via **True** chain, otherwise **False** chain is used. | 16 | +Must return a `boolean` value. If `true` - routes Message to subsequent rule nodes that are related via **True** link, |
17 | +otherwise sends Message to rule nodes related via **False** link. | ||
18 | +Uses 'Failure' link in case of any failures to evaluate the expression. | ||
17 | 19 | ||
18 | <div class="divider"></div> | 20 | <div class="divider"></div> |
19 | 21 | ||
20 | ##### Examples | 22 | ##### Examples |
21 | 23 | ||
22 | -* Forward all messages with `temperature` value greater than `20` to the **True** chain and all other messages to the **False** chain: | 24 | +* Forward all messages with `temperature` value greater than `20` to the **True** link and all other messages to the **False** link. |
25 | + Assumes that incoming messages always contain the 'temperature' field: | ||
23 | 26 | ||
24 | ```javascript | 27 | ```javascript |
25 | return msg.temperature > 20; | 28 | return msg.temperature > 20; |
26 | {:copy-code} | 29 | {:copy-code} |
27 | ``` | 30 | ``` |
28 | 31 | ||
32 | + | ||
33 | +Example of the rule chain configuration: | ||
34 | + | ||
35 | + | ||
36 | + | ||
37 | +* Same as above, but checks that the message has 'temperature' field to **avoid failures** on unexpected messages: | ||
38 | + | ||
39 | +```javascript | ||
40 | +return typeof msg.temperature !== 'undefined' && msg.temperature > 20; | ||
41 | +{:copy-code} | ||
42 | +``` | ||
43 | + | ||
29 | * Forward all messages with type `ATTRIBUTES_UPDATED` to the **True** chain and all other messages to the **False** chain: | 44 | * Forward all messages with type `ATTRIBUTES_UPDATED` to the **True** chain and all other messages to the **False** chain: |
30 | 45 | ||
31 | ```javascript | 46 | ```javascript |
@@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
5 | 5 | ||
6 | *function Generate(prevMsg, prevMetadata, prevMsgType): {msg: object, metadata: object, msgType: string}* | 6 | *function Generate(prevMsg, prevMetadata, prevMsgType): {msg: object, metadata: object, msgType: string}* |
7 | 7 | ||
8 | -JavaScript function generating new message using previous Message payload, Metadata and Message type as input arguments. | 8 | +JavaScript function generating new Message using previous Message payload, Metadata and Message type as input arguments. |
9 | 9 | ||
10 | **Parameters:** | 10 | **Parameters:** |
11 | 11 | ||
@@ -24,13 +24,13 @@ Should return the object with the following structure: | @@ -24,13 +24,13 @@ Should return the object with the following structure: | ||
24 | 24 | ||
25 | ```javascript | 25 | ```javascript |
26 | { | 26 | { |
27 | - msg?: {[key: string]: any}, | ||
28 | - metadata?: {[key: string]: string}, | ||
29 | - msgType?: string | 27 | + msg: {[key: string]: any}, |
28 | + metadata: {[key: string]: string}, | ||
29 | + msgType: string | ||
30 | } | 30 | } |
31 | ``` | 31 | ``` |
32 | 32 | ||
33 | -All fields in resulting object are optional and will be taken from previously generated Message if not specified. | 33 | +All fields in resulting object are mandatory. |
34 | 34 | ||
35 | <div class="divider"></div> | 35 | <div class="divider"></div> |
36 | 36 | ||
@@ -39,14 +39,11 @@ All fields in resulting object are optional and will be taken from previously ge | @@ -39,14 +39,11 @@ All fields in resulting object are optional and will be taken from previously ge | ||
39 | * Generate message of type `POST_TELEMETRY_REQUEST` with random `temperature` value from `18` to `32`: | 39 | * Generate message of type `POST_TELEMETRY_REQUEST` with random `temperature` value from `18` to `32`: |
40 | 40 | ||
41 | ```javascript | 41 | ```javascript |
42 | -var temperature = 18 + Math.random() * 14; | 42 | +var temperature = 18 + Math.random() * (32 - 18); |
43 | // Round to at most 2 decimal places (optional) | 43 | // Round to at most 2 decimal places (optional) |
44 | temperature = Math.round( temperature * 100 ) / 100; | 44 | temperature = Math.round( temperature * 100 ) / 100; |
45 | var msg = { temperature: temperature }; | 45 | var msg = { temperature: temperature }; |
46 | -var metadata = {}; | ||
47 | -var msgType = "POST_TELEMETRY_REQUEST"; | ||
48 | - | ||
49 | -return { msg: msg, metadata: metadata, msgType: msgType }; | 46 | +return { msg: msg, metadata: {}, msgType: "POST_TELEMETRY_REQUEST" }; |
50 | {:copy-code} | 47 | {:copy-code} |
51 | ``` | 48 | ``` |
52 | 49 | ||
@@ -62,9 +59,7 @@ and <strong>metadata</strong> with field <code>data</code> having value <code>40 | @@ -62,9 +59,7 @@ and <strong>metadata</strong> with field <code>data</code> having value <code>40 | ||
62 | ```javascript | 59 | ```javascript |
63 | var msg = { temp: 42, humidity: 77 }; | 60 | var msg = { temp: 42, humidity: 77 }; |
64 | var metadata = { data: 40 }; | 61 | var metadata = { data: 40 }; |
65 | -var msgType = "POST_TELEMETRY_REQUEST"; | ||
66 | - | ||
67 | -return { msg: msg, metadata: metadata, msgType: msgType }; | 62 | +return { msg: msg, metadata: metadata, msgType: "POST_TELEMETRY_REQUEST" }; |
68 | {:copy-code} | 63 | {:copy-code} |
69 | ``` | 64 | ``` |
70 | 65 | ||
@@ -108,9 +103,8 @@ if (isDecrement === 'true') { | @@ -108,9 +103,8 @@ if (isDecrement === 'true') { | ||
108 | 103 | ||
109 | var msg = { temperature: temperature }; | 104 | var msg = { temperature: temperature }; |
110 | var metadata = { isDecrement: isDecrement }; | 105 | var metadata = { isDecrement: isDecrement }; |
111 | -var msgType = "POST_TELEMETRY_REQUEST"; | ||
112 | 106 | ||
113 | -return { msg: msg, metadata: metadata, msgType: msgType }; | 107 | +return { msg: msg, metadata: metadata, msgType: "POST_TELEMETRY_REQUEST" }; |
114 | {:copy-code} | 108 | {:copy-code} |
115 | ``` | 109 | ``` |
116 | 110 |
@@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
5 | 5 | ||
6 | *function Switch(msg, metadata, msgType): string[]* | 6 | *function Switch(msg, metadata, msgType): string[]* |
7 | 7 | ||
8 | -JavaScript function computing **an array of next Relation names** for incoming Message. | 8 | +JavaScript function computing **an array of Link names** to forward the incoming Message. |
9 | 9 | ||
10 | **Parameters:** | 10 | **Parameters:** |
11 | 11 | ||
@@ -13,8 +13,9 @@ JavaScript function computing **an array of next Relation names** for incoming M | @@ -13,8 +13,9 @@ JavaScript function computing **an array of next Relation names** for incoming M | ||
13 | 13 | ||
14 | **Returns:** | 14 | **Returns:** |
15 | 15 | ||
16 | -Should return an array of `string` values presenting **next Relation names** where Message should be routed.<br> | ||
17 | -If returned array is empty - message will not be routed to any Node and discarded. | 16 | +Should return an array of `string` values presenting **link names** that the Rule Engine should use to further route the incoming Message.<br> |
17 | +If the result is an empty array - message will not be routed to any Node and will be immediately | ||
18 | +<a href="https://thingsboard.io/docs/user-guide/rule-engine-2-0/overview/#message-processing-result" target="_blank">acknowledged</a>. | ||
18 | 19 | ||
19 | <div class="divider"></div> | 20 | <div class="divider"></div> |
20 | 21 | ||
@@ -24,7 +25,7 @@ If returned array is empty - message will not be routed to any Node and discarde | @@ -24,7 +25,7 @@ If returned array is empty - message will not be routed to any Node and discarde | ||
24 | <li> | 25 | <li> |
25 | Forward all messages with <code>temperature</code> value greater than <code>30</code> to the <strong>'High temperature'</strong> chain,<br> | 26 | Forward all messages with <code>temperature</code> value greater than <code>30</code> to the <strong>'High temperature'</strong> chain,<br> |
26 | with <code>temperature</code> value lower than <code>20</code> to the <strong>'Low temperature'</strong> chain and all other messages<br> | 27 | with <code>temperature</code> value lower than <code>20</code> to the <strong>'Low temperature'</strong> chain and all other messages<br> |
27 | -to the <strong>'Normal temperature'</strong> chain: | 28 | +to the <strong>'Other'</strong> chain: |
28 | </li> | 29 | </li> |
29 | </ul> | 30 | </ul> |
30 | 31 | ||
@@ -34,11 +35,15 @@ if (msg.temperature > 30) { | @@ -34,11 +35,15 @@ if (msg.temperature > 30) { | ||
34 | } else if (msg.temperature < 20) { | 35 | } else if (msg.temperature < 20) { |
35 | return ['Low temperature']; | 36 | return ['Low temperature']; |
36 | } else { | 37 | } else { |
37 | - return ['Normal temperature']; | 38 | + return ['Other']; |
38 | } | 39 | } |
39 | {:copy-code} | 40 | {:copy-code} |
40 | ``` | 41 | ``` |
41 | 42 | ||
43 | +Example of the rule chain configuration: | ||
44 | + | ||
45 | + | ||
46 | + | ||
42 | <ul> | 47 | <ul> |
43 | <li> | 48 | <li> |
44 | For messages with type <code>POST_TELEMETRY_REQUEST</code>: | 49 | For messages with type <code>POST_TELEMETRY_REQUEST</code>: |
@@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
5 | 5 | ||
6 | *function Transform(msg, metadata, msgType): {msg: object, metadata: object, msgType: string}* | 6 | *function Transform(msg, metadata, msgType): {msg: object, metadata: object, msgType: string}* |
7 | 7 | ||
8 | -JavaScript function transforming input Message payload, Metadata or Message type. | 8 | +The JavaScript function to transform input Message payload, Metadata and/or Message type to the output message. |
9 | 9 | ||
10 | **Parameters:** | 10 | **Parameters:** |
11 | 11 | ||
@@ -29,11 +29,29 @@ All fields in resulting object are optional and will be taken from original mess | @@ -29,11 +29,29 @@ All fields in resulting object are optional and will be taken from original mess | ||
29 | 29 | ||
30 | ##### Examples | 30 | ##### Examples |
31 | 31 | ||
32 | -* Change message type to `CUSTOM_REQUEST`: | 32 | +* Add sum of two fields ('a' and 'b') as a new field ('sum') of existing message: |
33 | 33 | ||
34 | ```javascript | 34 | ```javascript |
35 | -return { msgType: 'CUSTOM_REQUEST' }; | ||
36 | -{:copy-code} | 35 | +if(typeof msg.a !== "undefined" && typeof msg.b !== "undefined"){ |
36 | + msg.sum = msg.a + msg.b; | ||
37 | +} | ||
38 | +return {msg: msg}; | ||
39 | +``` | ||
40 | + | ||
41 | +* Transform value of the 'temperature' field from °F to °C: | ||
42 | + | ||
43 | +```javascript | ||
44 | +msg.temperature = (msg.temperature - 32) * 5 / 9; | ||
45 | +return {msg: msg}; | ||
46 | +``` | ||
47 | + | ||
48 | +* Replace the incoming message with the new message that contains only one field - count of properties in the original message: | ||
49 | + | ||
50 | +```javascript | ||
51 | +var newMsg = { | ||
52 | + count: Object.keys(msg).length | ||
53 | +}; | ||
54 | +return {msg: newMsg}; | ||
37 | ``` | 55 | ``` |
38 | 56 | ||
39 | <ul> | 57 | <ul> |
@@ -17,67 +17,65 @@ A JavaScript function performing custom action. | @@ -17,67 +17,65 @@ A JavaScript function performing custom action. | ||
17 | 17 | ||
18 | ##### Examples | 18 | ##### Examples |
19 | 19 | ||
20 | -* Display alert dialog with entity information: | ||
21 | - | ||
22 | -```javascript | ||
23 | -{:code-style="max-height: 300px;"} | ||
24 | -var title; | ||
25 | -var content; | ||
26 | -if (entityName) { | ||
27 | - title = entityName + ' details'; | ||
28 | - content = '<b>Entity name</b>: ' + entityName; | ||
29 | - if (additionalParams && additionalParams.entity) { | ||
30 | - var entity = additionalParams.entity; | ||
31 | - if (entity.id) { | ||
32 | - content += '<br><b>Entity type</b>: ' + entity.id.entityType; | ||
33 | - } | ||
34 | - if (!isNaN(entity.temperature) && entity.temperature !== '') { | ||
35 | - content += '<br><b>Temperature</b>: ' + entity.temperature + ' °C'; | ||
36 | - } | ||
37 | - } | ||
38 | -} else { | ||
39 | - title = 'No entity information available'; | ||
40 | - content = '<b>No entity information available</b>'; | ||
41 | -} | ||
42 | - | ||
43 | -showAlertDialog(title, content); | ||
44 | - | ||
45 | -function showAlertDialog(title, content) { | ||
46 | - setTimeout(function() { | ||
47 | - widgetContext.dialogs.alert(title, content).subscribe(); | ||
48 | - }, 100); | ||
49 | -} | ||
50 | -{:copy-code} | ||
51 | -``` | ||
52 | - | ||
53 | -* Delete device after confirmation: | ||
54 | - | ||
55 | -```javascript | ||
56 | -{:code-style="max-height: 300px;"} | ||
57 | -var $injector = widgetContext.$scope.$injector; | ||
58 | -var dialogs = $injector.get(widgetContext.servicesMap.get('dialogs')); | ||
59 | -var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); | ||
60 | - | ||
61 | -openDeleteDeviceDialog(); | ||
62 | - | ||
63 | -function openDeleteDeviceDialog() { | ||
64 | - var title = 'Are you sure you want to delete the device ' + entityName + '?'; | ||
65 | - var content = 'Be careful, after the confirmation, the device and all related data will become unrecoverable!'; | ||
66 | - dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe( | ||
67 | - function(result) { | ||
68 | - if (result) { | ||
69 | - deleteDevice(); | ||
70 | - } | ||
71 | - } | ||
72 | - ); | ||
73 | -} | ||
74 | - | ||
75 | -function deleteDevice() { | ||
76 | - deviceService.deleteDevice(entityId.id).subscribe( | ||
77 | - function() { | ||
78 | - widgetContext.updateAliases(); | ||
79 | - } | ||
80 | - ); | ||
81 | -} | ||
82 | -{:copy-code} | ||
83 | -``` | 20 | +<br> |
21 | + | ||
22 | +<div style="padding-left: 32px;" | ||
23 | + tb-help-popup="widget/action/examples_custom_action/custom_action_display_alert" | ||
24 | + tb-help-popup-placement="top" | ||
25 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
26 | + trigger-style="font-size: 16px;" | ||
27 | + trigger-text="Display alert dialog with entity information"> | ||
28 | +</div> | ||
29 | + | ||
30 | +<br> | ||
31 | + | ||
32 | +<div style="padding-left: 32px;" | ||
33 | + tb-help-popup="widget/action/examples_custom_action/custom_action_delete_device_confirm" | ||
34 | + tb-help-popup-placement="top" | ||
35 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
36 | + trigger-style="font-size: 16px;" | ||
37 | + trigger-text="Delete device after confirmation"> | ||
38 | +</div> | ||
39 | + | ||
40 | +<br> | ||
41 | + | ||
42 | +<div style="padding-left: 32px;" | ||
43 | + tb-help-popup="widget/action/examples_custom_action/custom_action_return_previous_state" | ||
44 | + tb-help-popup-placement="top" | ||
45 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
46 | + trigger-style="font-size: 16px;" | ||
47 | + trigger-text="Return to the previous state"> | ||
48 | +</div> | ||
49 | + | ||
50 | +<br> | ||
51 | + | ||
52 | +<div style="padding-left: 32px;" | ||
53 | + tb-help-popup="widget/action/examples_custom_action/custom_action_open_state_save_parameters" | ||
54 | + tb-help-popup-placement="top" | ||
55 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
56 | + trigger-style="font-size: 16px;" | ||
57 | + trigger-text="Open state conditionally with saving particular state parameters"> | ||
58 | +</div> | ||
59 | + | ||
60 | +<br> | ||
61 | + | ||
62 | +<div style="padding-left: 32px;" | ||
63 | + tb-help-popup="widget/action/examples_custom_action/custom_action_back_first_and_open_state" | ||
64 | + tb-help-popup-placement="top" | ||
65 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
66 | + trigger-style="font-size: 16px;" | ||
67 | + trigger-text="Go back to the first state, after this go to the target state"> | ||
68 | +</div> | ||
69 | + | ||
70 | +<br> | ||
71 | + | ||
72 | +<div style="padding-left: 32px;" | ||
73 | + tb-help-popup="widget/action/examples_custom_action/custom_action_copy_access_token" | ||
74 | + tb-help-popup-placement="top" | ||
75 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
76 | + trigger-style="font-size: 16px;" | ||
77 | + trigger-text="Copy device access token to buffer"> | ||
78 | +</div> | ||
79 | + | ||
80 | +<br> | ||
81 | +<br> |
@@ -42,7 +42,7 @@ A JavaScript function performing custom action with defined HTML template to ren | @@ -42,7 +42,7 @@ A JavaScript function performing custom action with defined HTML template to ren | ||
42 | <br> | 42 | <br> |
43 | 43 | ||
44 | <div style="padding-left: 64px;" | 44 | <div style="padding-left: 64px;" |
45 | - tb-help-popup="widget/action/examples/custom_pretty_create_dialog_js" | 45 | + tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_create_dialog_js" |
46 | tb-help-popup-placement="top" | 46 | tb-help-popup-placement="top" |
47 | [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | 47 | [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" |
48 | trigger-style="font-size: 16px;" | 48 | trigger-style="font-size: 16px;" |
@@ -52,7 +52,7 @@ A JavaScript function performing custom action with defined HTML template to ren | @@ -52,7 +52,7 @@ A JavaScript function performing custom action with defined HTML template to ren | ||
52 | <br> | 52 | <br> |
53 | 53 | ||
54 | <div style="padding-left: 64px;" | 54 | <div style="padding-left: 64px;" |
55 | - tb-help-popup="widget/action/examples/custom_pretty_create_dialog_html" | 55 | + tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_create_dialog_html" |
56 | tb-help-popup-placement="top" | 56 | tb-help-popup-placement="top" |
57 | [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | 57 | [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" |
58 | trigger-style="font-size: 16px;" | 58 | trigger-style="font-size: 16px;" |
@@ -64,7 +64,7 @@ A JavaScript function performing custom action with defined HTML template to ren | @@ -64,7 +64,7 @@ A JavaScript function performing custom action with defined HTML template to ren | ||
64 | <br> | 64 | <br> |
65 | 65 | ||
66 | <div style="padding-left: 64px;" | 66 | <div style="padding-left: 64px;" |
67 | - tb-help-popup="widget/action/examples/custom_pretty_edit_dialog_js" | 67 | + tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_edit_dialog_js" |
68 | tb-help-popup-placement="top" | 68 | tb-help-popup-placement="top" |
69 | [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | 69 | [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" |
70 | trigger-style="font-size: 16px;" | 70 | trigger-style="font-size: 16px;" |
@@ -74,7 +74,73 @@ A JavaScript function performing custom action with defined HTML template to ren | @@ -74,7 +74,73 @@ A JavaScript function performing custom action with defined HTML template to ren | ||
74 | <br> | 74 | <br> |
75 | 75 | ||
76 | <div style="padding-left: 64px;" | 76 | <div style="padding-left: 64px;" |
77 | - tb-help-popup="widget/action/examples/custom_pretty_edit_dialog_html" | 77 | + tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_edit_dialog_html" |
78 | + tb-help-popup-placement="top" | ||
79 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
80 | + trigger-style="font-size: 16px;" | ||
81 | + trigger-text="HTML code"> | ||
82 | +</div> | ||
83 | + | ||
84 | +###### Display dialog to created new user | ||
85 | + | ||
86 | +<br> | ||
87 | + | ||
88 | +<div style="padding-left: 64px;" | ||
89 | + tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_create_user_js" | ||
90 | + tb-help-popup-placement="top" | ||
91 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
92 | + trigger-style="font-size: 16px;" | ||
93 | + trigger-text="JavaScript function"> | ||
94 | +</div> | ||
95 | + | ||
96 | +<br> | ||
97 | + | ||
98 | +<div style="padding-left: 64px;" | ||
99 | + tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_create_user_html" | ||
100 | + tb-help-popup-placement="top" | ||
101 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
102 | + trigger-style="font-size: 16px;" | ||
103 | + trigger-text="HTML code"> | ||
104 | +</div> | ||
105 | + | ||
106 | +###### Display dialog to add/edit image in entity attribute | ||
107 | + | ||
108 | +<br> | ||
109 | + | ||
110 | +<div style="padding-left: 64px;" | ||
111 | + tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_edit_image_js" | ||
112 | + tb-help-popup-placement="top" | ||
113 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
114 | + trigger-style="font-size: 16px;" | ||
115 | + trigger-text="JavaScript function"> | ||
116 | +</div> | ||
117 | + | ||
118 | +<br> | ||
119 | + | ||
120 | +<div style="padding-left: 64px;" | ||
121 | + tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_edit_image_html" | ||
122 | + tb-help-popup-placement="top" | ||
123 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
124 | + trigger-style="font-size: 16px;" | ||
125 | + trigger-text="HTML code"> | ||
126 | +</div> | ||
127 | + | ||
128 | +###### Display dialog to clone device | ||
129 | + | ||
130 | +<br> | ||
131 | + | ||
132 | +<div style="padding-left: 64px;" | ||
133 | + tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_clone_device_js" | ||
134 | + tb-help-popup-placement="top" | ||
135 | + [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | ||
136 | + trigger-style="font-size: 16px;" | ||
137 | + trigger-text="JavaScript function"> | ||
138 | +</div> | ||
139 | + | ||
140 | +<br> | ||
141 | + | ||
142 | +<div style="padding-left: 64px;" | ||
143 | + tb-help-popup="widget/action/examples_custom_pretty/custom_pretty_clone_device_html" | ||
78 | tb-help-popup-placement="top" | 144 | tb-help-popup-placement="top" |
79 | [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" | 145 | [tb-help-popup-style]="{maxHeight: '50vh', maxWidth: '50vw'}" |
80 | trigger-style="font-size: 16px;" | 146 | trigger-style="font-size: 16px;" |
1 | +#### Function go back to the first state, after this go to the target state | ||
2 | + | ||
3 | +```javascript | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +var stateIndex = widgetContext.stateController.getStateIndex(); | ||
6 | +while (stateIndex > 0) { | ||
7 | + stateIndex -= 1; | ||
8 | + backToPrevState(stateIndex); | ||
9 | +} | ||
10 | +openDashboardState('devices'); | ||
11 | + | ||
12 | +function backToPrevState(stateIndex) { | ||
13 | + widgetContext.stateController.navigatePrevState(stateIndex); | ||
14 | +} | ||
15 | + | ||
16 | +function openDashboardState(statedId) { | ||
17 | + var currentState = widgetContext.stateController.getStateId(); | ||
18 | + if (currentState !== statedId) { | ||
19 | + var params = {}; | ||
20 | + widgetContext.stateController.updateState(statedId, params, false); | ||
21 | + } | ||
22 | +} | ||
23 | +{:copy-code} | ||
24 | +``` | ||
25 | + | ||
26 | +<br> | ||
27 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_copy_access_token.md
0 → 100644
1 | +#### Function copy device access token to buffer | ||
2 | + | ||
3 | +```javascript | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +var $injector = widgetContext.$scope.$injector; | ||
6 | +var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); | ||
7 | +var $translate = $injector.get(widgetContext.servicesMap.get('translate')); | ||
8 | +var $scope = widgetContext.$scope; | ||
9 | +if (entityId.id && entityId.entityType === 'DEVICE') { | ||
10 | + deviceService.getDeviceCredentials(entityId.id, true).subscribe( | ||
11 | + (deviceCredentials) => { | ||
12 | + var credentialsId = deviceCredentials.credentialsId; | ||
13 | + if (copyToClipboard(credentialsId)) { | ||
14 | + $scope.showSuccessToast($translate.instant('device.accessTokenCopiedMessage'), 750, "top", "left"); | ||
15 | + } | ||
16 | + } | ||
17 | + ); | ||
18 | +} | ||
19 | + | ||
20 | +function copyToClipboard(text) { | ||
21 | + if (window.clipboardData && window.clipboardData.setData) { | ||
22 | + return window.clipboardData.setData("Text", text); | ||
23 | + } | ||
24 | + else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { | ||
25 | + var textarea = document.createElement("textarea"); | ||
26 | + textarea.textContent = text; | ||
27 | + textarea.style.position = "fixed"; | ||
28 | + document.body.appendChild(textarea); | ||
29 | + textarea.select(); | ||
30 | + try { | ||
31 | + return document.execCommand("copy"); | ||
32 | + } | ||
33 | + catch (ex) { | ||
34 | + console.warn("Copy to clipboard failed.", ex); | ||
35 | + return false; | ||
36 | + } | ||
37 | + document.body.removeChild(textarea); | ||
38 | + } | ||
39 | +} | ||
40 | +{:copy-code} | ||
41 | +``` | ||
42 | + | ||
43 | +<br> | ||
44 | +<br> |
1 | +#### Function delete device after confirmation | ||
2 | + | ||
3 | +```javascript | ||
4 | +var $injector = widgetContext.$scope.$injector; | ||
5 | +var dialogs = $injector.get(widgetContext.servicesMap.get('dialogs')); | ||
6 | +var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); | ||
7 | + | ||
8 | +openDeleteDeviceDialog(); | ||
9 | + | ||
10 | +function openDeleteDeviceDialog() { | ||
11 | + var title = 'Are you sure you want to delete the device ' + entityName + '?'; | ||
12 | + var content = 'Be careful, after the confirmation, the device and all related data will become unrecoverable!'; | ||
13 | + dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe( | ||
14 | + function(result) { | ||
15 | + if (result) { | ||
16 | + deleteDevice(); | ||
17 | + } | ||
18 | + } | ||
19 | + ); | ||
20 | +} | ||
21 | + | ||
22 | +function deleteDevice() { | ||
23 | + deviceService.deleteDevice(entityId.id).subscribe( | ||
24 | + function() { | ||
25 | + widgetContext.updateAliases(); | ||
26 | + } | ||
27 | + ); | ||
28 | +} | ||
29 | +{:copy-code} | ||
30 | +``` | ||
31 | + | ||
32 | +<br> | ||
33 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_display_alert.md
0 → 100644
1 | +#### Function display alert dialog with entity information | ||
2 | + | ||
3 | +```javascript | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +var title; | ||
6 | +var content; | ||
7 | +if (entityName) { | ||
8 | + title = entityName + ' details'; | ||
9 | + content = '<b>Entity name</b>: ' + entityName; | ||
10 | + if (additionalParams && additionalParams.entity) { | ||
11 | + var entity = additionalParams.entity; | ||
12 | + if (entity.id) { | ||
13 | + content += '<br><b>Entity type</b>: ' + entity.id.entityType; | ||
14 | + } | ||
15 | + if (!isNaN(entity.temperature) && entity.temperature !== '') { | ||
16 | + content += '<br><b>Temperature</b>: ' + entity.temperature + ' °C'; | ||
17 | + } | ||
18 | + } | ||
19 | +} else { | ||
20 | + title = 'No entity information available'; | ||
21 | + content = '<b>No entity information available</b>'; | ||
22 | +} | ||
23 | + | ||
24 | +showAlertDialog(title, content); | ||
25 | + | ||
26 | +function showAlertDialog(title, content) { | ||
27 | + setTimeout(function() { | ||
28 | + widgetContext.dialogs.alert(title, content).subscribe(); | ||
29 | + }, 100); | ||
30 | +} | ||
31 | +{:copy-code} | ||
32 | +``` | ||
33 | + | ||
34 | +<br> | ||
35 | +<br> |
1 | +#### Function open state conditionally with saving particular state parameters | ||
2 | + | ||
3 | +```javascript | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +var entitySubType; | ||
6 | +var $injector = widgetContext.$scope.$injector; | ||
7 | +$injector.get(widgetContext.servicesMap.get('entityService')).getEntity(entityId.entityType, entityId.id) | ||
8 | + .subscribe(function(data) { | ||
9 | + entitySubType = data.type; | ||
10 | + if (entitySubType == 'energy meter') { | ||
11 | + openDashboardStates('energy_meter_details_view'); | ||
12 | + } else if (entitySubType == 'thermometer') { | ||
13 | + openDashboardStates('thermometer_details_view'); | ||
14 | + } | ||
15 | + }); | ||
16 | + | ||
17 | +function openDashboardStates(statedId) { | ||
18 | + var stateParams = widgetContext.stateController.getStateParams(); | ||
19 | + var params = { | ||
20 | + entityId: entityId, | ||
21 | + entityName: entityName | ||
22 | + }; | ||
23 | + | ||
24 | + if (stateParams.city) { | ||
25 | + params.city = stateParams.city; | ||
26 | + } | ||
27 | + | ||
28 | + widgetContext.stateController.openState(statedId, params, false); | ||
29 | +} | ||
30 | +{:copy-code} | ||
31 | +``` | ||
32 | + | ||
33 | +<br> | ||
34 | +<br> |
1 | +#### Function return to the previous state | ||
2 | + | ||
3 | +```javascript | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +let stateIndex = widgetContext.stateController.getStateIndex(); | ||
6 | +if (stateIndex > 0) { | ||
7 | + stateIndex -= 1; | ||
8 | + backToPrevState(stateIndex); | ||
9 | +} | ||
10 | + | ||
11 | +function backToPrevState(stateIndex) { | ||
12 | + widgetContext.stateController.navigatePrevState(stateIndex); | ||
13 | +} | ||
14 | +{:copy-code} | ||
15 | +``` | ||
16 | + | ||
17 | +<br> | ||
18 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_html.md
0 → 100644
1 | +#### HTML template of dialog to clone device | ||
2 | + | ||
3 | +```html | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +<form [formGroup]="cloneDeviceFormGroup" (ngSubmit)="save()" style="min-width:320px;"> | ||
6 | + <mat-toolbar fxLayout="row" color="primary"> | ||
7 | + <h2>Clone device: {{ deviceName }}</h2> | ||
8 | + <span fxFlex></span> | ||
9 | + <button mat-icon-button (click)="cancel()" | ||
10 | + type="button"> | ||
11 | + <mat-icon class="material-icons">close | ||
12 | + </mat-icon> | ||
13 | + </button> | ||
14 | + </mat-toolbar> | ||
15 | + <mat-progress-bar color="warn" mode="indeterminate" | ||
16 | + *ngIf="isLoading$ | async"> | ||
17 | + </mat-progress-bar> | ||
18 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | ||
19 | + <div mat-dialog-content fxLayout="column"> | ||
20 | + <mat-form-field fxFlex class="mat-block"> | ||
21 | + <mat-label>Clone device name</mat-label> | ||
22 | + <input matInput formControlName="cloneName" required> | ||
23 | + <mat-error *ngIf="cloneDeviceFormGroup.get('cloneName').hasError('required')"> | ||
24 | + Clone device name is required | ||
25 | + </mat-error> | ||
26 | + </mat-form-field> | ||
27 | + </div> | ||
28 | + <div mat-dialog-actions fxLayout="row" | ||
29 | + fxLayoutAlign="end center"> | ||
30 | + <button mat-button color="primary" type="button" | ||
31 | + [disabled]="(isLoading$ | async)" | ||
32 | + (click)="cancel()" cdkFocusInitial> | ||
33 | + Cancel | ||
34 | + </button> | ||
35 | + <button mat-button mat-raised-button color="primary" | ||
36 | + type="submit" | ||
37 | + [disabled]="(isLoading$ | async) || cloneDeviceFormGroup.invalid || !cloneDeviceFormGroup.dirty"> | ||
38 | + Save | ||
39 | + </button> | ||
40 | + </div> | ||
41 | +</form> | ||
42 | +{:copy-code} | ||
43 | +``` | ||
44 | + | ||
45 | +<br> | ||
46 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_js.md
0 → 100644
1 | +#### Function displaying dialog to clone device | ||
2 | + | ||
3 | +```javascript | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +const $injector = widgetContext.$scope.$injector; | ||
6 | +const customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); | ||
7 | +const attributeService = $injector.get(widgetContext.servicesMap.get('attributeService')); | ||
8 | +const deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); | ||
9 | +const rxjs = widgetContext.rxjs; | ||
10 | + | ||
11 | +openCloneDeviceDialog(); | ||
12 | + | ||
13 | +function openCloneDeviceDialog() { | ||
14 | + customDialog.customDialog(htmlTemplate, CloneDeviceDialogController).subscribe(); | ||
15 | +} | ||
16 | + | ||
17 | +function CloneDeviceDialogController(instance) { | ||
18 | + let vm = instance; | ||
19 | + vm.deviceName = entityName; | ||
20 | + | ||
21 | + vm.cloneDeviceFormGroup = vm.fb.group({ | ||
22 | + cloneName: ['', [vm.validators.required]] | ||
23 | + }); | ||
24 | + | ||
25 | + vm.save = function() { | ||
26 | + deviceService.getDevice(entityId.id).pipe( | ||
27 | + rxjs.mergeMap((origDevice) => { | ||
28 | + let cloneDevice = { | ||
29 | + name: vm.cloneDeviceFormGroup.get('cloneName').value, | ||
30 | + type: origDevice.type | ||
31 | + }; | ||
32 | + return deviceService.saveDevice(cloneDevice).pipe( | ||
33 | + rxjs.mergeMap((newDevice) => { | ||
34 | + return attributeService.getEntityAttributes(origDevice.id, 'SERVER_SCOPE').pipe( | ||
35 | + rxjs.mergeMap((origAttributes) => { | ||
36 | + return attributeService.saveEntityAttributes(newDevice.id, 'SERVER_SCOPE', origAttributes); | ||
37 | + }) | ||
38 | + ); | ||
39 | + }) | ||
40 | + ); | ||
41 | + }) | ||
42 | + ).subscribe(() => { | ||
43 | + widgetContext.updateAliases(); | ||
44 | + vm.dialogRef.close(null); | ||
45 | + }); | ||
46 | + }; | ||
47 | + | ||
48 | + vm.cancel = function() { | ||
49 | + vm.dialogRef.close(null); | ||
50 | + }; | ||
51 | +} | ||
52 | +{:copy-code} | ||
53 | +``` | ||
54 | + | ||
55 | +<br> | ||
56 | +<br> |
1 | +#### HTML template of dialog to create a device or an asset | ||
2 | + | ||
3 | +```html | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +<form #addEntityForm="ngForm" [formGroup]="addEntityFormGroup" | ||
6 | + (ngSubmit)="save()" class="add-entity-form"> | ||
7 | + <mat-toolbar fxLayout="row" color="primary"> | ||
8 | + <h2>Add entity</h2> | ||
9 | + <span fxFlex></span> | ||
10 | + <button mat-icon-button (click)="cancel()" type="button"> | ||
11 | + <mat-icon class="material-icons">close</mat-icon> | ||
12 | + </button> | ||
13 | + </mat-toolbar> | ||
14 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | ||
15 | + </mat-progress-bar> | ||
16 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | ||
17 | + <div mat-dialog-content fxLayout="column"> | ||
18 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
19 | + <mat-form-field fxFlex class="mat-block"> | ||
20 | + <mat-label>Entity Name</mat-label> | ||
21 | + <input matInput formControlName="entityName" required> | ||
22 | + <mat-error *ngIf="addEntityFormGroup.get('entityName').hasError('required')"> | ||
23 | + Entity name is required. | ||
24 | + </mat-error> | ||
25 | + </mat-form-field> | ||
26 | + <mat-form-field fxFlex class="mat-block"> | ||
27 | + <mat-label>Entity Label</mat-label> | ||
28 | + <input matInput formControlName="entityLabel" > | ||
29 | + </mat-form-field> | ||
30 | + </div> | ||
31 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
32 | + <tb-entity-type-select | ||
33 | + class="mat-block" | ||
34 | + formControlName="entityType" | ||
35 | + [showLabel]="true" | ||
36 | + [allowedEntityTypes]="allowedEntityTypes" | ||
37 | + ></tb-entity-type-select> | ||
38 | + <tb-entity-subtype-autocomplete | ||
39 | + fxFlex *ngIf="addEntityFormGroup.get('entityType').value == 'ASSET'" | ||
40 | + class="mat-block" | ||
41 | + formControlName="type" | ||
42 | + [required]="true" | ||
43 | + [entityType]="'ASSET'" | ||
44 | + ></tb-entity-subtype-autocomplete> | ||
45 | + <tb-entity-subtype-autocomplete | ||
46 | + fxFlex *ngIf="addEntityFormGroup.get('entityType').value != 'ASSET'" | ||
47 | + class="mat-block" | ||
48 | + formControlName="type" | ||
49 | + [required]="true" | ||
50 | + [entityType]="'DEVICE'" | ||
51 | + ></tb-entity-subtype-autocomplete> | ||
52 | + </div> | ||
53 | + <div formGroupName="attributes" fxLayout="column"> | ||
54 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
55 | + <mat-form-field fxFlex class="mat-block"> | ||
56 | + <mat-label>Latitude</mat-label> | ||
57 | + <input type="number" step="any" matInput formControlName="latitude"> | ||
58 | + </mat-form-field> | ||
59 | + <mat-form-field fxFlex class="mat-block"> | ||
60 | + <mat-label>Longitude</mat-label> | ||
61 | + <input type="number" step="any" matInput formControlName="longitude"> | ||
62 | + </mat-form-field> | ||
63 | + </div> | ||
64 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
65 | + <mat-form-field fxFlex class="mat-block"> | ||
66 | + <mat-label>Address</mat-label> | ||
67 | + <input matInput formControlName="address"> | ||
68 | + </mat-form-field> | ||
69 | + <mat-form-field fxFlex class="mat-block"> | ||
70 | + <mat-label>Owner</mat-label> | ||
71 | + <input matInput formControlName="owner"> | ||
72 | + </mat-form-field> | ||
73 | + </div> | ||
74 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
75 | + <mat-form-field fxFlex class="mat-block"> | ||
76 | + <mat-label>Integer Value</mat-label> | ||
77 | + <input type="number" step="1" matInput formControlName="number"> | ||
78 | + <mat-error *ngIf="addEntityFormGroup.get('attributes.number').hasError('pattern')"> | ||
79 | + Invalid integer value. | ||
80 | + </mat-error> | ||
81 | + </mat-form-field> | ||
82 | + <div class="boolean-value-input" fxLayout="column" fxLayoutAlign="center start" fxFlex> | ||
83 | + <label class="checkbox-label">Boolean Value</label> | ||
84 | + <mat-checkbox formControlName="booleanValue" style="margin-bottom: 40px;"> | ||
85 | + | ||
86 | + </mat-checkbox> | ||
87 | + </div> | ||
88 | + </div> | ||
89 | + </div> | ||
90 | + <div class="relations-list"> | ||
91 | + <div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div> | ||
92 | + <div class="body" [fxShow]="relations().length"> | ||
93 | + <div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="relations" *ngFor="let relation of relations().controls; let i = index;"> | ||
94 | + <div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;"> | ||
95 | + <div fxFlex fxLayout="column"> | ||
96 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
97 | + <mat-form-field class="mat-block" style="min-width: 100px;"> | ||
98 | + <mat-label>Direction</mat-label> | ||
99 | + <mat-select formControlName="direction" name="direction"> | ||
100 | + <mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value"> | ||
101 | + | ||
102 | + </mat-option> | ||
103 | + </mat-select> | ||
104 | + <mat-error *ngIf="relation.get('direction').hasError('required')"> | ||
105 | + Relation direction is required. | ||
106 | + </mat-error> | ||
107 | + </mat-form-field> | ||
108 | + <tb-relation-type-autocomplete | ||
109 | + fxFlex class="mat-block" | ||
110 | + formControlName="relationType" | ||
111 | + [required]="true"> | ||
112 | + </tb-relation-type-autocomplete> | ||
113 | + </div> | ||
114 | + <div fxLayout="row" fxLayout.xs="column"> | ||
115 | + <tb-entity-select | ||
116 | + fxFlex class="mat-block" | ||
117 | + [required]="true" | ||
118 | + formControlName="relatedEntity"> | ||
119 | + </tb-entity-select> | ||
120 | + </div> | ||
121 | + </div> | ||
122 | + <div fxLayout="column" fxLayoutAlign="center center"> | ||
123 | + <button mat-icon-button color="primary" | ||
124 | + aria-label="Remove" | ||
125 | + type="button" | ||
126 | + (click)="removeRelation(i)" | ||
127 | + matTooltip="Remove relation" | ||
128 | + matTooltipPosition="above"> | ||
129 | + <mat-icon>close</mat-icon> | ||
130 | + </button> | ||
131 | + </div> | ||
132 | + </div> | ||
133 | + </div> | ||
134 | + </div> | ||
135 | + <div> | ||
136 | + <button mat-raised-button color="primary" | ||
137 | + type="button" | ||
138 | + (click)="addRelation()" | ||
139 | + matTooltip="Add Relation" | ||
140 | + matTooltipPosition="above"> | ||
141 | + Add | ||
142 | + </button> | ||
143 | + </div> | ||
144 | + </div> | ||
145 | + </div> | ||
146 | + <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> | ||
147 | + <button mat-button color="primary" | ||
148 | + type="button" | ||
149 | + [disabled]="(isLoading$ | async)" | ||
150 | + (click)="cancel()" cdkFocusInitial> | ||
151 | + Cancel | ||
152 | + </button> | ||
153 | + <button mat-button mat-raised-button color="primary" | ||
154 | + type="submit" | ||
155 | + [disabled]="(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty"> | ||
156 | + Create | ||
157 | + </button> | ||
158 | + </div> | ||
159 | +</form> | ||
160 | +{:copy-code} | ||
161 | +``` | ||
162 | + | ||
163 | +<br> | ||
164 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_js.md
0 → 100644
1 | +#### Function displaying dialog to create a device or an asset | ||
2 | + | ||
3 | +```javascript | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +let $injector = widgetContext.$scope.$injector; | ||
6 | +let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); | ||
7 | +let assetService = $injector.get(widgetContext.servicesMap.get('assetService')); | ||
8 | +let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); | ||
9 | +let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService')); | ||
10 | +let entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService')); | ||
11 | + | ||
12 | +openAddEntityDialog(); | ||
13 | + | ||
14 | +function openAddEntityDialog() { | ||
15 | + customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe(); | ||
16 | +} | ||
17 | + | ||
18 | +function AddEntityDialogController(instance) { | ||
19 | + let vm = instance; | ||
20 | + | ||
21 | + vm.allowedEntityTypes = ['ASSET', 'DEVICE']; | ||
22 | + vm.entitySearchDirection = { | ||
23 | + from: "FROM", | ||
24 | + to: "TO" | ||
25 | + } | ||
26 | + | ||
27 | + vm.addEntityFormGroup = vm.fb.group({ | ||
28 | + entityName: ['', [vm.validators.required]], | ||
29 | + entityType: ['DEVICE'], | ||
30 | + entityLabel: [null], | ||
31 | + type: ['', [vm.validators.required]], | ||
32 | + attributes: vm.fb.group({ | ||
33 | + latitude: [null], | ||
34 | + longitude: [null], | ||
35 | + address: [null], | ||
36 | + owner: [null], | ||
37 | + number: [null, [vm.validators.pattern(/^-?[0-9]+$/)]], | ||
38 | + booleanValue: [null] | ||
39 | + }), | ||
40 | + relations: vm.fb.array([]) | ||
41 | + }); | ||
42 | + | ||
43 | + vm.cancel = function () { | ||
44 | + vm.dialogRef.close(null); | ||
45 | + }; | ||
46 | + | ||
47 | + vm.relations = function () { | ||
48 | + return vm.addEntityFormGroup.get('relations'); | ||
49 | + }; | ||
50 | + | ||
51 | + vm.addRelation = function () { | ||
52 | + vm.relations().push(vm.fb.group({ | ||
53 | + relatedEntity: [null, [vm.validators.required]], | ||
54 | + relationType: [null, [vm.validators.required]], | ||
55 | + direction: [null, [vm.validators.required]] | ||
56 | + })); | ||
57 | + }; | ||
58 | + | ||
59 | + vm.removeRelation = function (index) { | ||
60 | + vm.relations().removeAt(index); | ||
61 | + vm.relations().markAsDirty(); | ||
62 | + }; | ||
63 | + | ||
64 | + vm.save = function () { | ||
65 | + vm.addEntityFormGroup.markAsPristine(); | ||
66 | + saveEntityObservable().subscribe( | ||
67 | + function (entity) { | ||
68 | + widgetContext.rxjs.forkJoin([ | ||
69 | + saveAttributes(entity.id), | ||
70 | + saveRelations(entity.id) | ||
71 | + ]).subscribe( | ||
72 | + function () { | ||
73 | + widgetContext.updateAliases(); | ||
74 | + vm.dialogRef.close(null); | ||
75 | + } | ||
76 | + ); | ||
77 | + } | ||
78 | + ); | ||
79 | + }; | ||
80 | + | ||
81 | + function saveEntityObservable() { | ||
82 | + const formValues = vm.addEntityFormGroup.value; | ||
83 | + let entity = { | ||
84 | + name: formValues.entityName, | ||
85 | + type: formValues.type, | ||
86 | + label: formValues.entityLabel | ||
87 | + }; | ||
88 | + if (formValues.entityType == 'ASSET') { | ||
89 | + return assetService.saveAsset(entity); | ||
90 | + } else if (formValues.entityType == 'DEVICE') { | ||
91 | + return deviceService.saveDevice(entity); | ||
92 | + } | ||
93 | + } | ||
94 | + | ||
95 | + function saveAttributes(entityId) { | ||
96 | + let attributes = vm.addEntityFormGroup.get('attributes').value; | ||
97 | + let attributesArray = []; | ||
98 | + for (let key in attributes) { | ||
99 | + if (attributes[key] !== null) { | ||
100 | + attributesArray.push({key: key, value: attributes[key]}); | ||
101 | + } | ||
102 | + } | ||
103 | + if (attributesArray.length > 0) { | ||
104 | + return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray); | ||
105 | + } | ||
106 | + return widgetContext.rxjs.of([]); | ||
107 | + } | ||
108 | + | ||
109 | + function saveRelations(entityId) { | ||
110 | + let relations = vm.addEntityFormGroup.get('relations').value; | ||
111 | + let tasks = []; | ||
112 | + for (let i = 0; i < relations.length; i++) { | ||
113 | + let relation = { | ||
114 | + type: relations[i].relationType, | ||
115 | + typeGroup: 'COMMON' | ||
116 | + }; | ||
117 | + if (relations[i].direction == 'FROM') { | ||
118 | + relation.to = relations[i].relatedEntity; | ||
119 | + relation.from = entityId; | ||
120 | + } else { | ||
121 | + relation.to = entityId; | ||
122 | + relation.from = relations[i].relatedEntity; | ||
123 | + } | ||
124 | + tasks.push(entityRelationService.saveRelation(relation)); | ||
125 | + } | ||
126 | + if (tasks.length > 0) { | ||
127 | + return widgetContext.rxjs.forkJoin(tasks); | ||
128 | + } | ||
129 | + return widgetContext.rxjs.of([]); | ||
130 | + } | ||
131 | +} | ||
132 | +{:copy-code} | ||
133 | +``` | ||
134 | + | ||
135 | +<br> | ||
136 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_html.md
0 → 100644
1 | +#### HTML template of dialog to create new user | ||
2 | + | ||
3 | +```html | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +<form [formGroup]="addEntityFormGroup" (ngSubmit)="save()" style="min-width:480px;"> | ||
6 | + <mat-toolbar fxLayout="row" color="primary"> | ||
7 | + <h2>Add new User</h2> | ||
8 | + <span fxFlex></span> | ||
9 | + <button mat-icon-button (click)="cancel()" type="button"> | ||
10 | + <mat-icon class="material-icons">close</mat-icon> | ||
11 | + </button> | ||
12 | + </mat-toolbar> | ||
13 | + <mat-progress-bar color="warn" mode="indeterminate" | ||
14 | + *ngIf="isLoading$ | async"> | ||
15 | + </mat-progress-bar> | ||
16 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"> | ||
17 | + </div> | ||
18 | + <div mat-dialog-content fxLayout="column"> | ||
19 | + <div fxLayout="row" fxLayoutGap="8px" | ||
20 | + fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
21 | + <mat-form-field fxFlex class="mat-block"> | ||
22 | + <mat-label>Email</mat-label> | ||
23 | + <input matInput formControlName="email" required | ||
24 | + 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,}))$/'> | ||
25 | + <mat-error *ngIf="addEntityFormGroup.get('email').hasError('required')"> | ||
26 | + Email is required | ||
27 | + </mat-error> | ||
28 | + <mat-error *ngIf="addEntityFormGroup.get('email').hasError('pattern')"> | ||
29 | + Invalid email format | ||
30 | + </mat-error> | ||
31 | + </mat-form-field> | ||
32 | + </div> | ||
33 | + <div fxLayout="row" fxLayoutGap="8px" | ||
34 | + fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
35 | + <mat-form-field fxFlex class="mat-block"> | ||
36 | + <mat-label>First Name</mat-label> | ||
37 | + <input matInput | ||
38 | + formControlName="firstName"> | ||
39 | + </mat-form-field> | ||
40 | + </div> | ||
41 | + <div fxLayout="row" fxLayoutGap="8px" | ||
42 | + fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
43 | + <mat-form-field fxFlex class="mat-block"> | ||
44 | + <mat-label>Last Name</mat-label> | ||
45 | + <input matInput | ||
46 | + formControlName="lastName"> | ||
47 | + </mat-form-field> | ||
48 | + </div> | ||
49 | + <div fxLayout="row" fxLayoutGap="8px" | ||
50 | + fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
51 | + <mat-form-field fxFlex class="mat-block"> | ||
52 | + <mat-label>User activation method</mat-label> | ||
53 | + <mat-select matInput formControlName="userActivationMethod"> | ||
54 | + <mat-option *ngFor="let activationMethod of activationMethods" [value]="activationMethod.value"> | ||
55 | + {{activationMethod.name}} | ||
56 | + </mat-option> | ||
57 | + </mat-select> | ||
58 | + <mat-error *ngIf="addEntityFormGroup.get('userActivationMethod').hasError('required')">Please choose activation method</mat-error> | ||
59 | + <mat-hint>e.g. Send activation email</mat-hint> | ||
60 | + </mat-form-field> | ||
61 | + </div> | ||
62 | + </div> | ||
63 | + <div mat-dialog-actions fxLayout="row" | ||
64 | + fxLayoutAlign="end center"> | ||
65 | + <button mat-button color="primary" type="button" | ||
66 | + [disabled]="(isLoading$ | async)" | ||
67 | + (click)="cancel()" cdkFocusInitial> | ||
68 | + Cancel | ||
69 | + </button> | ||
70 | + <button mat-button mat-raised-button color="primary" | ||
71 | + type="submit" | ||
72 | + [disabled]="(isLoading$ | async) || addEntityFormGroup.invalid || !addEntityFormGroup.dirty"> | ||
73 | + Create | ||
74 | + </button> | ||
75 | + </div> | ||
76 | +</form> | ||
77 | +{:copy-code} | ||
78 | +``` | ||
79 | + | ||
80 | +<br> | ||
81 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_js.md
0 → 100644
1 | +#### Function displaying dialog to create new user | ||
2 | + | ||
3 | +```javascript | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +const $injector = widgetContext.$scope.$injector; | ||
6 | +const customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); | ||
7 | +const userService = $injector.get(widgetContext.servicesMap.get('userService')); | ||
8 | +const $scope = widgetContext.$scope; | ||
9 | +const rxjs = widgetContext.rxjs; | ||
10 | + | ||
11 | +openAddUserDialog(); | ||
12 | + | ||
13 | +function openAddUserDialog() { | ||
14 | + customDialog.customDialog(htmlTemplate, AddUserDialogController).subscribe(); | ||
15 | +} | ||
16 | + | ||
17 | +function AddUserDialogController(instance) { | ||
18 | + let vm = instance; | ||
19 | + | ||
20 | + vm.currentUser = widgetContext.currentUser; | ||
21 | + | ||
22 | + vm.addEntityFormGroup = vm.fb.group({ | ||
23 | + 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,}))$/)]], | ||
24 | + firstName: [''], | ||
25 | + lastName: ['', ], | ||
26 | + userActivationMethod: ['', [vm.validators.required]] | ||
27 | + }); | ||
28 | + | ||
29 | + vm.activationMethods = [ | ||
30 | + { | ||
31 | + value: 'displayActivationLink', | ||
32 | + name: 'Display activation link' | ||
33 | + }, | ||
34 | + { | ||
35 | + value: 'sendActivationMail', | ||
36 | + name: 'Send activation email' | ||
37 | + } | ||
38 | + ]; | ||
39 | + | ||
40 | + vm.cancel = function() { | ||
41 | + vm.dialogRef.close(null); | ||
42 | + }; | ||
43 | + | ||
44 | + vm.save = function() { | ||
45 | + let formObj = vm.addEntityFormGroup.getRawValue(); | ||
46 | + let attributes = []; | ||
47 | + let sendActivationMail = false; | ||
48 | + let newUser = { | ||
49 | + email: formObj.email, | ||
50 | + firstName: formObj.firstName, | ||
51 | + lastName: formObj.lastName, | ||
52 | + authority: 'TENANT_ADMIN' | ||
53 | + }; | ||
54 | + | ||
55 | + if (formObj.userActivationMethod === 'sendActivationMail') { | ||
56 | + sendActivationMail = true; | ||
57 | + } | ||
58 | + | ||
59 | + userService.saveUser(newUser, sendActivationMail).pipe( | ||
60 | + rxjs.mergeMap((user) => { | ||
61 | + let activationObs; | ||
62 | + if (sendActivationMail) { | ||
63 | + activationObs = rxjs.of(null); | ||
64 | + } else { | ||
65 | + activationObs = userService.getActivationLink(user.id.id); | ||
66 | + } | ||
67 | + return activationObs.pipe( | ||
68 | + rxjs.mergeMap((activationLink) => { | ||
69 | + return activationLink ? customDialog.customDialog(activationLinkDialogTemplate, ActivationLinkDialogController, {"activationLink": activationLink}) : rxjs.of(null); | ||
70 | + }) | ||
71 | + ); | ||
72 | + }) | ||
73 | + ).subscribe(() => { | ||
74 | + vm.dialogRef.close(null); | ||
75 | + }); | ||
76 | + }; | ||
77 | +} | ||
78 | + | ||
79 | +function ActivationLinkDialogController(instance) { | ||
80 | + let vm = instance; | ||
81 | + | ||
82 | + vm.activationLink = vm.data.activationLink; | ||
83 | + | ||
84 | + vm.onActivationLinkCopied = () => { | ||
85 | + $scope.showSuccessToast("User activation link has been copied to clipboard", 1200, "bottom", "left", "activationLinkDialogContent"); | ||
86 | + }; | ||
87 | + | ||
88 | + vm.close = () => { | ||
89 | + vm.dialogRef.close(null); | ||
90 | + }; | ||
91 | +} | ||
92 | + | ||
93 | +let activationLinkDialogTemplate = `<form style="min-width: 400px; position: relative;"> | ||
94 | + <mat-toolbar color="primary"> | ||
95 | + <h2 translate>user.activation-link</h2> | ||
96 | + <span fxFlex></span> | ||
97 | + <button mat-button mat-icon-button | ||
98 | + (click)="close()" | ||
99 | + type="button"> | ||
100 | + <mat-icon class="material-icons">close</mat-icon> | ||
101 | + </button> | ||
102 | + </mat-toolbar> | ||
103 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | ||
104 | + </mat-progress-bar> | ||
105 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | ||
106 | + <div mat-dialog-content tb-toast toastTarget="activationLinkDialogContent"> | ||
107 | + <div class="mat-content" fxLayout="column"> | ||
108 | + <span [innerHTML]="'user.activation-link-text' | translate: {activationLink: activationLink}"></span> | ||
109 | + <div fxLayout="row" fxLayoutAlign="start center"> | ||
110 | + <pre class="tb-highlight" fxFlex><code>{{ activationLink }}</code></pre> | ||
111 | + <button mat-icon-button | ||
112 | + color="primary" | ||
113 | + ngxClipboard | ||
114 | + cbContent="{{ activationLink }}" | ||
115 | + (cbOnSuccess)="onActivationLinkCopied()" | ||
116 | + matTooltip="{{ 'user.copy-activation-link' | translate }}" | ||
117 | + matTooltipPosition="above"> | ||
118 | + <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> | ||
119 | + </button> | ||
120 | + </div> | ||
121 | + </div> | ||
122 | + </div> | ||
123 | + <div mat-dialog-actions fxLayoutAlign="end center"> | ||
124 | + <button mat-button color="primary" | ||
125 | + type="button" | ||
126 | + cdkFocusInitial | ||
127 | + [disabled]="(isLoading$ | async)" | ||
128 | + (click)="close()"> | ||
129 | + {{ 'action.ok' | translate }} | ||
130 | + </button> | ||
131 | + </div> | ||
132 | +</form>`; | ||
133 | +{:copy-code} | ||
134 | +``` | ||
135 | + | ||
136 | +<br> | ||
137 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_html.md
0 → 100644
1 | +#### HTML template of dialog to edit a device or an asset | ||
2 | + | ||
3 | +```html | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +<form #editEntityForm="ngForm" [formGroup]="editEntityFormGroup" | ||
6 | + (ngSubmit)="save()" class="edit-entity-form"> | ||
7 | + <mat-toolbar fxLayout="row" color="primary"> | ||
8 | + <h2>Edit </h2> | ||
9 | + <span fxFlex></span> | ||
10 | + <button mat-icon-button (click)="cancel()" type="button"> | ||
11 | + <mat-icon class="material-icons">close</mat-icon> | ||
12 | + </button> | ||
13 | + </mat-toolbar> | ||
14 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | ||
15 | + </mat-progress-bar> | ||
16 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | ||
17 | + <div mat-dialog-content fxLayout="column"> | ||
18 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
19 | + <mat-form-field fxFlex class="mat-block"> | ||
20 | + <mat-label>Entity Name</mat-label> | ||
21 | + <input matInput formControlName="entityName" required readonly=""> | ||
22 | + </mat-form-field> | ||
23 | + <mat-form-field fxFlex class="mat-block"> | ||
24 | + <mat-label>Entity Label</mat-label> | ||
25 | + <input matInput formControlName="entityLabel"> | ||
26 | + </mat-form-field> | ||
27 | + </div> | ||
28 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
29 | + <mat-form-field fxFlex class="mat-block"> | ||
30 | + <mat-label>Entity Type</mat-label> | ||
31 | + <input matInput formControlName="entityType" readonly> | ||
32 | + </mat-form-field> | ||
33 | + <mat-form-field fxFlex class="mat-block"> | ||
34 | + <mat-label>Type</mat-label> | ||
35 | + <input matInput formControlName="type" readonly> | ||
36 | + </mat-form-field> | ||
37 | + </div> | ||
38 | + <div formGroupName="attributes" fxLayout="column"> | ||
39 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
40 | + <mat-form-field fxFlex class="mat-block"> | ||
41 | + <mat-label>Latitude</mat-label> | ||
42 | + <input type="number" step="any" matInput formControlName="latitude"> | ||
43 | + </mat-form-field> | ||
44 | + <mat-form-field fxFlex class="mat-block"> | ||
45 | + <mat-label>Longitude</mat-label> | ||
46 | + <input type="number" step="any" matInput formControlName="longitude"> | ||
47 | + </mat-form-field> | ||
48 | + </div> | ||
49 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
50 | + <mat-form-field fxFlex class="mat-block"> | ||
51 | + <mat-label>Address</mat-label> | ||
52 | + <input matInput formControlName="address"> | ||
53 | + </mat-form-field> | ||
54 | + <mat-form-field fxFlex class="mat-block"> | ||
55 | + <mat-label>Owner</mat-label> | ||
56 | + <input matInput formControlName="owner"> | ||
57 | + </mat-form-field> | ||
58 | + </div> | ||
59 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
60 | + <mat-form-field fxFlex class="mat-block"> | ||
61 | + <mat-label>Integer Value</mat-label> | ||
62 | + <input type="number" step="1" matInput formControlName="number"> | ||
63 | + <mat-error *ngIf="editEntityFormGroup.get('attributes.number').hasError('pattern')"> | ||
64 | + Invalid integer value. | ||
65 | + </mat-error> | ||
66 | + </mat-form-field> | ||
67 | + <div class="boolean-value-input" fxLayout="column" fxLayoutAlign="center start" fxFlex> | ||
68 | + <label class="checkbox-label">Boolean Value</label> | ||
69 | + <mat-checkbox formControlName="booleanValue" style="margin-bottom: 40px;"> | ||
70 | + | ||
71 | + </mat-checkbox> | ||
72 | + </div> | ||
73 | + </div> | ||
74 | + </div> | ||
75 | + <div class="relations-list old-relations"> | ||
76 | + <div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div> | ||
77 | + <div class="body" [fxShow]="oldRelations().length"> | ||
78 | + <div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="oldRelations" | ||
79 | + *ngFor="let relation of oldRelations().controls; let i = index;"> | ||
80 | + <div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;"> | ||
81 | + <div fxFlex fxLayout="column"> | ||
82 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
83 | + <mat-form-field class="mat-block" style="min-width: 100px;"> | ||
84 | + <mat-label>Direction</mat-label> | ||
85 | + <mat-select formControlName="direction" name="direction"> | ||
86 | + <mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value"> | ||
87 | + | ||
88 | + </mat-option> | ||
89 | + </mat-select> | ||
90 | + <mat-error *ngIf="relation.get('direction').hasError('required')"> | ||
91 | + Relation direction is required. | ||
92 | + </mat-error> | ||
93 | + </mat-form-field> | ||
94 | + <tb-relation-type-autocomplete | ||
95 | + fxFlex class="mat-block" | ||
96 | + formControlName="relationType" | ||
97 | + required="true"> | ||
98 | + </tb-relation-type-autocomplete> | ||
99 | + </div> | ||
100 | + <div fxLayout="row" fxLayout.xs="column"> | ||
101 | + <tb-entity-select | ||
102 | + fxFlex class="mat-block" | ||
103 | + required="true" | ||
104 | + formControlName="relatedEntity"> | ||
105 | + </tb-entity-select> | ||
106 | + </div> | ||
107 | + </div> | ||
108 | + <div fxLayout="column" fxLayoutAlign="center center"> | ||
109 | + <button mat-icon-button color="primary" | ||
110 | + aria-label="Remove" | ||
111 | + type="button" | ||
112 | + (click)="removeOldRelation(i)" | ||
113 | + matTooltip="Remove relation" | ||
114 | + matTooltipPosition="above"> | ||
115 | + <mat-icon>close</mat-icon> | ||
116 | + </button> | ||
117 | + </div> | ||
118 | + </div> | ||
119 | + </div> | ||
120 | + </div> | ||
121 | + </div> | ||
122 | + <div class="relations-list"> | ||
123 | + <div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">New Relations</div> | ||
124 | + <div class="body" [fxShow]="relations().length"> | ||
125 | + <div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="relations" *ngFor="let relation of relations().controls; let i = index;"> | ||
126 | + <div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;"> | ||
127 | + <div fxFlex fxLayout="column"> | ||
128 | + <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> | ||
129 | + <mat-form-field class="mat-block" style="min-width: 100px;"> | ||
130 | + <mat-label>Direction</mat-label> | ||
131 | + <mat-select formControlName="direction" name="direction"> | ||
132 | + <mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value"> | ||
133 | + | ||
134 | + </mat-option> | ||
135 | + </mat-select> | ||
136 | + <mat-error *ngIf="relation.get('direction').hasError('required')"> | ||
137 | + Relation direction is required. | ||
138 | + </mat-error> | ||
139 | + </mat-form-field> | ||
140 | + <tb-relation-type-autocomplete | ||
141 | + fxFlex class="mat-block" | ||
142 | + formControlName="relationType" | ||
143 | + [required]="true"> | ||
144 | + </tb-relation-type-autocomplete> | ||
145 | + </div> | ||
146 | + <div fxLayout="row" fxLayout.xs="column"> | ||
147 | + <tb-entity-select | ||
148 | + fxFlex class="mat-block" | ||
149 | + [required]="true" | ||
150 | + formControlName="relatedEntity"> | ||
151 | + </tb-entity-select> | ||
152 | + </div> | ||
153 | + </div> | ||
154 | + <div fxLayout="column" fxLayoutAlign="center center"> | ||
155 | + <button mat-icon-button color="primary" | ||
156 | + aria-label="Remove" | ||
157 | + type="button" | ||
158 | + (click)="removeRelation(i)" | ||
159 | + matTooltip="Remove relation" | ||
160 | + matTooltipPosition="above"> | ||
161 | + <mat-icon>close</mat-icon> | ||
162 | + </button> | ||
163 | + </div> | ||
164 | + </div> | ||
165 | + </div> | ||
166 | + </div> | ||
167 | + <div> | ||
168 | + <button mat-raised-button color="primary" | ||
169 | + type="button" | ||
170 | + (click)="addRelation()" | ||
171 | + matTooltip="Add Relation" | ||
172 | + matTooltipPosition="above"> | ||
173 | + Add | ||
174 | + </button> | ||
175 | + </div> | ||
176 | + </div> | ||
177 | + </div> | ||
178 | + <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> | ||
179 | + <button mat-button color="primary" | ||
180 | + type="button" | ||
181 | + [disabled]="(isLoading$ | async)" | ||
182 | + (click)="cancel()" cdkFocusInitial> | ||
183 | + Cancel | ||
184 | + </button> | ||
185 | + <button mat-button mat-raised-button color="primary" | ||
186 | + type="submit" | ||
187 | + [disabled]="(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty"> | ||
188 | + Save | ||
189 | + </button> | ||
190 | + </div> | ||
191 | +</form> | ||
192 | +{:copy-code} | ||
193 | +``` | ||
194 | + | ||
195 | +<br> | ||
196 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_js.md
0 → 100644
1 | +#### Function displaying dialog to edit a device or an asset | ||
2 | + | ||
3 | +```javascript | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +let $injector = widgetContext.$scope.$injector; | ||
6 | +let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); | ||
7 | +let entityService = $injector.get(widgetContext.servicesMap.get('entityService')); | ||
8 | +let assetService = $injector.get(widgetContext.servicesMap.get('assetService')); | ||
9 | +let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); | ||
10 | +let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService')); | ||
11 | +let entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService')); | ||
12 | + | ||
13 | +openEditEntityDialog(); | ||
14 | + | ||
15 | +function openEditEntityDialog() { | ||
16 | + customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe(); | ||
17 | +} | ||
18 | + | ||
19 | +function EditEntityDialogController(instance) { | ||
20 | + let vm = instance; | ||
21 | + | ||
22 | + vm.entityName = entityName; | ||
23 | + vm.entityType = entityId.entityType; | ||
24 | + vm.entitySearchDirection = { | ||
25 | + from: "FROM", | ||
26 | + to: "TO" | ||
27 | + }; | ||
28 | + vm.attributes = {}; | ||
29 | + vm.oldRelationsData = []; | ||
30 | + vm.relationsToDelete = []; | ||
31 | + vm.entity = {}; | ||
32 | + | ||
33 | + vm.editEntityFormGroup = vm.fb.group({ | ||
34 | + entityName: ['', [vm.validators.required]], | ||
35 | + entityType: [null], | ||
36 | + entityLabel: [null], | ||
37 | + type: ['', [vm.validators.required]], | ||
38 | + attributes: vm.fb.group({ | ||
39 | + latitude: [null], | ||
40 | + longitude: [null], | ||
41 | + address: [null], | ||
42 | + owner: [null], | ||
43 | + number: [null, [vm.validators.pattern(/^-?[0-9]+$/)]], | ||
44 | + booleanValue: [false] | ||
45 | + }), | ||
46 | + oldRelations: vm.fb.array([]), | ||
47 | + relations: vm.fb.array([]) | ||
48 | + }); | ||
49 | + | ||
50 | + getEntityInfo(); | ||
51 | + | ||
52 | + vm.cancel = function() { | ||
53 | + vm.dialogRef.close(null); | ||
54 | + }; | ||
55 | + | ||
56 | + vm.relations = function() { | ||
57 | + return vm.editEntityFormGroup.get('relations'); | ||
58 | + }; | ||
59 | + | ||
60 | + vm.oldRelations = function() { | ||
61 | + return vm.editEntityFormGroup.get('oldRelations'); | ||
62 | + }; | ||
63 | + | ||
64 | + vm.addRelation = function() { | ||
65 | + vm.relations().push(vm.fb.group({ | ||
66 | + relatedEntity: [null, [vm.validators.required]], | ||
67 | + relationType: [null, [vm.validators.required]], | ||
68 | + direction: [null, [vm.validators.required]] | ||
69 | + })); | ||
70 | + }; | ||
71 | + | ||
72 | + function addOldRelation() { | ||
73 | + vm.oldRelations().push(vm.fb.group({ | ||
74 | + relatedEntity: [{value: null, disabled: true}, [vm.validators.required]], | ||
75 | + relationType: [{value: null, disabled: true}, [vm.validators.required]], | ||
76 | + direction: [{value: null, disabled: true}, [vm.validators.required]] | ||
77 | + })); | ||
78 | + } | ||
79 | + | ||
80 | + vm.removeRelation = function(index) { | ||
81 | + vm.relations().removeAt(index); | ||
82 | + vm.relations().markAsDirty(); | ||
83 | + }; | ||
84 | + | ||
85 | + vm.removeOldRelation = function(index) { | ||
86 | + vm.oldRelations().removeAt(index); | ||
87 | + vm.relationsToDelete.push(vm.oldRelationsData[index]); | ||
88 | + vm.oldRelations().markAsDirty(); | ||
89 | + }; | ||
90 | + | ||
91 | + vm.save = function() { | ||
92 | + vm.editEntityFormGroup.markAsPristine(); | ||
93 | + widgetContext.rxjs.forkJoin([ | ||
94 | + saveAttributes(entityId), | ||
95 | + saveRelations(entityId), | ||
96 | + saveEntity() | ||
97 | + ]).subscribe( | ||
98 | + function () { | ||
99 | + widgetContext.updateAliases(); | ||
100 | + vm.dialogRef.close(null); | ||
101 | + } | ||
102 | + ); | ||
103 | + }; | ||
104 | + | ||
105 | + function getEntityAttributes(attributes) { | ||
106 | + for (var i = 0; i < attributes.length; i++) { | ||
107 | + vm.attributes[attributes[i].key] = attributes[i].value; | ||
108 | + } | ||
109 | + } | ||
110 | + | ||
111 | + function getEntityRelations(relations) { | ||
112 | + let relationsFrom = relations[0]; | ||
113 | + let relationsTo = relations[1]; | ||
114 | + for (let i=0; i < relationsFrom.length; i++) { | ||
115 | + let relation = { | ||
116 | + direction: 'FROM', | ||
117 | + relationType: relationsFrom[i].type, | ||
118 | + relatedEntity: relationsFrom[i].to | ||
119 | + }; | ||
120 | + vm.oldRelationsData.push(relation); | ||
121 | + addOldRelation(); | ||
122 | + } | ||
123 | + for (let i=0; i < relationsTo.length; i++) { | ||
124 | + let relation = { | ||
125 | + direction: 'TO', | ||
126 | + relationType: relationsTo[i].type, | ||
127 | + relatedEntity: relationsTo[i].from | ||
128 | + }; | ||
129 | + vm.oldRelationsData.push(relation); | ||
130 | + addOldRelation(); | ||
131 | + } | ||
132 | + } | ||
133 | + | ||
134 | + function getEntityInfo() { | ||
135 | + widgetContext.rxjs.forkJoin([ | ||
136 | + entityRelationService.findInfoByFrom(entityId), | ||
137 | + entityRelationService.findInfoByTo(entityId), | ||
138 | + attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE'), | ||
139 | + entityService.getEntity(entityId.entityType, entityId.id) | ||
140 | + ]).subscribe( | ||
141 | + function (data) { | ||
142 | + getEntityRelations(data.slice(0,2)); | ||
143 | + getEntityAttributes(data[2]); | ||
144 | + vm.entity = data[3]; | ||
145 | + vm.editEntityFormGroup.patchValue({ | ||
146 | + entityName: vm.entity.name, | ||
147 | + entityType: vm.entityType, | ||
148 | + entityLabel: vm.entity.label, | ||
149 | + type: vm.entity.type, | ||
150 | + attributes: vm.attributes, | ||
151 | + oldRelations: vm.oldRelationsData | ||
152 | + }, {emitEvent: false}); | ||
153 | + } | ||
154 | + ); | ||
155 | + } | ||
156 | + | ||
157 | + function saveEntity() { | ||
158 | + const formValues = vm.editEntityFormGroup.value; | ||
159 | + if (vm.entity.label !== formValues.entityLabel){ | ||
160 | + vm.entity.label = formValues.entityLabel; | ||
161 | + if (formValues.entityType == 'ASSET') { | ||
162 | + return assetService.saveAsset(vm.entity); | ||
163 | + } else if (formValues.entityType == 'DEVICE') { | ||
164 | + return deviceService.saveDevice(vm.entity); | ||
165 | + } | ||
166 | + } | ||
167 | + return widgetContext.rxjs.of([]); | ||
168 | + } | ||
169 | + | ||
170 | + function saveAttributes(entityId) { | ||
171 | + let attributes = vm.editEntityFormGroup.get('attributes').value; | ||
172 | + let attributesArray = []; | ||
173 | + for (let key in attributes) { | ||
174 | + if (attributes[key] !== vm.attributes[key]) { | ||
175 | + attributesArray.push({key: key, value: attributes[key]}); | ||
176 | + } | ||
177 | + } | ||
178 | + if (attributesArray.length > 0) { | ||
179 | + return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray); | ||
180 | + } | ||
181 | + return widgetContext.rxjs.of([]); | ||
182 | + } | ||
183 | + | ||
184 | + function saveRelations(entityId) { | ||
185 | + let relations = vm.editEntityFormGroup.get('relations').value; | ||
186 | + let tasks = []; | ||
187 | + for(let i=0; i < relations.length; i++) { | ||
188 | + let relation = { | ||
189 | + type: relations[i].relationType, | ||
190 | + typeGroup: 'COMMON' | ||
191 | + }; | ||
192 | + if (relations[i].direction == 'FROM') { | ||
193 | + relation.to = relations[i].relatedEntity; | ||
194 | + relation.from = entityId; | ||
195 | + } else { | ||
196 | + relation.to = entityId; | ||
197 | + relation.from = relations[i].relatedEntity; | ||
198 | + } | ||
199 | + tasks.push(entityRelationService.saveRelation(relation)); | ||
200 | + } | ||
201 | + for (let i=0; i < vm.relationsToDelete.length; i++) { | ||
202 | + let relation = { | ||
203 | + type: vm.relationsToDelete[i].relationType | ||
204 | + }; | ||
205 | + if (vm.relationsToDelete[i].direction == 'FROM') { | ||
206 | + relation.to = vm.relationsToDelete[i].relatedEntity; | ||
207 | + relation.from = entityId; | ||
208 | + } else { | ||
209 | + relation.to = entityId; | ||
210 | + relation.from = vm.relationsToDelete[i].relatedEntity; | ||
211 | + } | ||
212 | + tasks.push(entityRelationService.deleteRelation(relation.from, relation.type, relation.to)); | ||
213 | + } | ||
214 | + if (tasks.length > 0) { | ||
215 | + return widgetContext.rxjs.forkJoin(tasks); | ||
216 | + } | ||
217 | + return widgetContext.rxjs.of([]); | ||
218 | + } | ||
219 | +} | ||
220 | +{:copy-code} | ||
221 | +``` | ||
222 | + | ||
223 | +<br> | ||
224 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_html.md
0 → 100644
1 | +#### HTML template of dialog to add/edit image in entity attribute | ||
2 | + | ||
3 | +```html | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +<form [formGroup]="editEntity" (ngSubmit)="save()" class="edit-entity-form"> | ||
6 | + <mat-toolbar fxLayout="row" color="primary"> | ||
7 | + <h2>Edit {{entityName}} image</h2> | ||
8 | + <span fxFlex></span> | ||
9 | + <button mat-icon-button (click)="cancel()" type="button"> | ||
10 | + <mat-icon class="material-icons">close</mat-icon> | ||
11 | + </button> | ||
12 | + </mat-toolbar> | ||
13 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="(isLoading$ | async) || loading"> | ||
14 | + </mat-progress-bar> | ||
15 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async) && !loading"></div> | ||
16 | + <div mat-dialog-content fxLayout="column"> | ||
17 | + <div formGroupName="attributes" fxLayout="column"> | ||
18 | + <tb-image-input | ||
19 | + label="Entity image" | ||
20 | + formControlName="image" | ||
21 | + ></tb-image-input> | ||
22 | + </div> | ||
23 | + </div> | ||
24 | + <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> | ||
25 | + <button mat-button mat-raised-button color="primary" | ||
26 | + type="submit" | ||
27 | + [disabled]="(isLoading$ | async) || editEntity.invalid || !editEntity.dirty"> | ||
28 | + Save | ||
29 | + </button> | ||
30 | + <button mat-button color="primary" | ||
31 | + type="button" | ||
32 | + [disabled]="(isLoading$ | async)" | ||
33 | + (click)="cancel()" cdkFocusInitial> | ||
34 | + Cancel | ||
35 | + </button> | ||
36 | + </div> | ||
37 | +</form> | ||
38 | +{:copy-code} | ||
39 | +``` | ||
40 | + | ||
41 | +<br> | ||
42 | +<br> |
ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_js.md
0 → 100644
1 | +#### Function displaying dialog to add/edit image in entity attribute | ||
2 | + | ||
3 | +```javascript | ||
4 | +{:code-style="max-height: 400px;"} | ||
5 | +let $injector = widgetContext.$scope.$injector; | ||
6 | +let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); | ||
7 | +let assetService = $injector.get(widgetContext.servicesMap.get('assetService')); | ||
8 | +let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService')); | ||
9 | +let entityService = $injector.get(widgetContext.servicesMap.get('entityService')); | ||
10 | + | ||
11 | +openAddEntityDialog(); | ||
12 | + | ||
13 | +function openAddEntityDialog() { | ||
14 | + customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe(() => {}); | ||
15 | +} | ||
16 | + | ||
17 | +function AddEntityDialogController(instance) { | ||
18 | + let vm = instance; | ||
19 | + | ||
20 | + vm.entityName = entityName; | ||
21 | + | ||
22 | + vm.attributes = {}; | ||
23 | + | ||
24 | + vm.editEntity = vm.fb.group({ | ||
25 | + attributes: vm.fb.group({ | ||
26 | + image: [null] | ||
27 | + }) | ||
28 | + }); | ||
29 | + | ||
30 | + getEntityInfo(); | ||
31 | + | ||
32 | + vm.cancel = function() { | ||
33 | + vm.dialogRef.close(null); | ||
34 | + }; | ||
35 | + | ||
36 | + vm.save = function() { | ||
37 | + vm.loading = true; | ||
38 | + saveAttributes(entityId).subscribe( | ||
39 | + () => { | ||
40 | + vm.dialogRef.close(null); | ||
41 | + }, () =>{ | ||
42 | + vm.loading = false; | ||
43 | + } | ||
44 | + ); | ||
45 | + }; | ||
46 | + | ||
47 | + function getEntityAttributes(attributes) { | ||
48 | + for (var i = 0; i < attributes.length; i++) { | ||
49 | + vm.attributes[attributes[i].key] = attributes[i].value; | ||
50 | + } | ||
51 | + } | ||
52 | + | ||
53 | + function getEntityInfo() { | ||
54 | + vm.loading = true; | ||
55 | + attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe( | ||
56 | + function (data) { | ||
57 | + getEntityAttributes(data); | ||
58 | + | ||
59 | + vm.editEntity.patchValue({ | ||
60 | + attributes: vm.attributes | ||
61 | + }, {emitEvent: false}); | ||
62 | + vm.loading = false; | ||
63 | + } | ||
64 | + ); | ||
65 | + } | ||
66 | + | ||
67 | + function saveAttributes(entityId) { | ||
68 | + let attributes = vm.editEntity.get('attributes').value; | ||
69 | + let attributesArray = []; | ||
70 | + for (let key in attributes) { | ||
71 | + if (attributes[key] !== vm.attributes[key]) { | ||
72 | + attributesArray.push({key: key, value: attributes[key]}); | ||
73 | + } | ||
74 | + } | ||
75 | + if (attributesArray.length > 0) { | ||
76 | + return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray); | ||
77 | + } | ||
78 | + return widgetContext.rxjs.of([]); | ||
79 | + } | ||
80 | +} | ||
81 | +{:copy-code} | ||
82 | +``` | ||
83 | + | ||
84 | +<br> | ||
85 | +<br> |
@@ -54,6 +54,33 @@ if (prevOrigValue) { | @@ -54,6 +54,33 @@ if (prevOrigValue) { | ||
54 | } | 54 | } |
55 | {:copy-code} | 55 | {:copy-code} |
56 | ``` | 56 | ``` |
57 | +* Formatting data to time format | ||
58 | + | ||
59 | +```javascript | ||
60 | +if (value) { | ||
61 | + return moment(value).format("DD/MM/YYYY HH:mm:ss"); | ||
62 | +} | ||
63 | +return ''; | ||
64 | +{:copy-code} | ||
65 | +``` | ||
66 | + | ||
67 | +* Creates line-breaks for 0 values, when used in line chart | ||
68 | + | ||
69 | +```javascript | ||
70 | +if (value === 0) { | ||
71 | + return null; | ||
72 | +} else { | ||
73 | + return value; | ||
74 | +} | ||
75 | +{:copy-code} | ||
76 | +``` | ||
77 | + | ||
78 | +* Display data point of the HTML value card under the condition | ||
79 | + | ||
80 | +```javascript | ||
81 | +return value ? '<div class="info"><b>Temperature: </b>'+value+' °C</div>' : ''; | ||
82 | +{:copy-code} | ||
83 | +``` | ||
57 | 84 | ||
58 | <br> | 85 | <br> |
59 | <br> | 86 | <br> |
@@ -57,5 +57,87 @@ return '<div style="border: 2px solid #0072ff; ' + | @@ -57,5 +57,87 @@ return '<div style="border: 2px solid #0072ff; ' + | ||
57 | {:copy-code} | 57 | {:copy-code} |
58 | ``` | 58 | ``` |
59 | 59 | ||
60 | +* Colored circles instead of boolean value: | ||
61 | + | ||
62 | +```javascript | ||
63 | +var color; | ||
64 | +var active = value; | ||
65 | +if (active == 'true') { // all key values here are strings | ||
66 | + color = '#27AE60'; | ||
67 | +} else { | ||
68 | + color = '#EB5757'; | ||
69 | +} | ||
70 | +return '<span style="font-size: 18px; color: ' + color + '">⬤</span>'; | ||
71 | +{:copy-code} | ||
72 | +``` | ||
73 | + | ||
74 | +* Decimal value format (1196 => 1,196.0): | ||
75 | + | ||
76 | +```javascript | ||
77 | +var value = value / 1; | ||
78 | +function numberWithCommas(x) { | ||
79 | + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | ||
80 | +} | ||
81 | +return value ? numberWithCommas(value.toFixed(1)) : ''; | ||
82 | +{:copy-code} | ||
83 | +``` | ||
84 | + | ||
85 | +* Show device status and icon this : | ||
86 | + | ||
87 | +```javascript | ||
88 | +{:code-style="max-height: 200px; max-width: 850px;"} | ||
89 | +function getIcon(value) { | ||
90 | + if (value == 'QUEUED') { | ||
91 | + return '<mat-icon class="mat-icon material-icons mat-icon-no-color" data-mat-icon-type="font" style="color: #000;">' + | ||
92 | + '<svg style="width:24px;height:24px" viewBox="0 0 24 24">' + | ||
93 | + '<path fill="currentColor" d="M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z" />' + | ||
94 | + '</svg>' + | ||
95 | + '</mat-icon>'; | ||
96 | + } | ||
97 | + if (value == 'UPDATED' ) { | ||
98 | + return '<mat-icon class="mat-icon notranslate material-icons mat-icon-no-color" data-mat-icon-type="font" style="color: #000">' + | ||
99 | + 'update' + | ||
100 | + '</mat-icon>'; | ||
101 | + } | ||
102 | + return ''; | ||
103 | +} | ||
104 | +function capitalize (s) { | ||
105 | + if (typeof s !== 'string') return ''; | ||
106 | + return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); | ||
107 | +} | ||
108 | +var status = value; | ||
109 | +return getIcon(status) + '<span style="vertical-align: super;padding-left: 8px;">' + capitalize(status) + '</span>'; | ||
110 | +{:copy-code} | ||
111 | +``` | ||
112 | + | ||
113 | +* Display device attribute value on progress bar: | ||
114 | + | ||
115 | +```javascript | ||
116 | +{:code-style="max-height: 200px; max-width: 850px;"} | ||
117 | +var progress = value; | ||
118 | +if (value !== '') { | ||
119 | + return `<mat-progress-bar style="height: 8px; padding-right: 30px" ` + | ||
120 | + `role="progressbar" aria-valuemin="0" aria-valuemax="100" ` + | ||
121 | + `tabindex="-1" mode="determinate" value="${progress}" ` + | ||
122 | + `class="mat-progress-bar mat-primary" aria-valuenow="${progress}">` + | ||
123 | + `<div aria-hidden="true">` + | ||
124 | + `<svg width="100%" height="8" focusable="false" ` + | ||
125 | + `class="mat-progress-bar-background mat-progress-bar-element">` + | ||
126 | + `<defs>` + | ||
127 | + `<pattern x="4" y="0" width="8" height="4" patternUnits="userSpaceOnUse" id="mat-progress-bar-0">` + | ||
128 | + `<circle cx="2" cy="2" r="2"></circle>` + | ||
129 | + `</pattern>` + | ||
130 | + `</defs>` + | ||
131 | + `<rect width="100%" height="100%" fill="url("/components/progress-bar/overview#mat-progress-bar-0")"></rect>` + | ||
132 | + `</svg>` + | ||
133 | + `<div class="mat-progress-bar-buffer mat-progress-bar-element"></div>` + | ||
134 | + `<div class="mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element" style="transform: scale3d(${progress / 100}, 1, 1);"></div>` + | ||
135 | + `<div class="mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element"></div>` + | ||
136 | + `</div>` + | ||
137 | + `</mat-progress-bar>`; | ||
138 | +} | ||
139 | +{:copy-code} | ||
140 | +``` | ||
141 | + | ||
60 | <br> | 142 | <br> |
61 | <br> | 143 | <br> |
@@ -29,6 +29,16 @@ Should return key/value object presenting style attributes. | @@ -29,6 +29,16 @@ Should return key/value object presenting style attributes. | ||
29 | 29 | ||
30 | ##### Examples | 30 | ##### Examples |
31 | 31 | ||
32 | +* Set color and font-weight table cell content: | ||
33 | + | ||
34 | +```javascript | ||
35 | +return { | ||
36 | + color:'rgb(0, 132, 214)', | ||
37 | + fontWeight: 600 | ||
38 | +} | ||
39 | +{:copy-code} | ||
40 | +``` | ||
41 | + | ||
32 | * Set color depending on device temperature value: | 42 | * Set color depending on device temperature value: |
33 | 43 | ||
34 | ```javascript | 44 | ```javascript |
@@ -27,6 +27,16 @@ Should return key/value object presenting style attributes. | @@ -27,6 +27,16 @@ Should return key/value object presenting style attributes. | ||
27 | 27 | ||
28 | ##### Examples | 28 | ##### Examples |
29 | 29 | ||
30 | +* Set color and font-weight table row: | ||
31 | + | ||
32 | +```javascript | ||
33 | +return { | ||
34 | + color:'rgb(0, 132, 214)', | ||
35 | + fontWeight: 600 | ||
36 | +} | ||
37 | +{:copy-code} | ||
38 | +``` | ||
39 | + | ||
30 | * Set row background color depending on device type: | 40 | * Set row background color depending on device type: |
31 | 41 | ||
32 | ```javascript | 42 | ```javascript |
@@ -43,6 +43,17 @@ return ''; | @@ -43,6 +43,17 @@ return ''; | ||
43 | {:copy-code} | 43 | {:copy-code} |
44 | ``` | 44 | ``` |
45 | 45 | ||
46 | +* Present ticks in decimal format (1196 => 1,196.0): | ||
47 | + | ||
48 | +```javascript | ||
49 | +var value = value / 1; | ||
50 | +function numberWithCommas(x) { | ||
51 | + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | ||
52 | +} | ||
53 | +return value ? numberWithCommas(value.toFixed(1)) : ''; | ||
54 | +{:copy-code} | ||
55 | +``` | ||
56 | + | ||
46 | <ul> | 57 | <ul> |
47 | <li> | 58 | <li> |
48 | To present axis ticks for true / false or 1 / 0 data.<br> | 59 | To present axis ticks for true / false or 1 / 0 data.<br> |
@@ -36,5 +36,16 @@ return value.toFixed(2) + ' A'; | @@ -36,5 +36,16 @@ return value.toFixed(2) + ' A'; | ||
36 | {:copy-code} | 36 | {:copy-code} |
37 | ``` | 37 | ``` |
38 | 38 | ||
39 | +* Present the datapoint value in decimal format (1196 => 1,196.0): | ||
40 | + | ||
41 | +```javascript | ||
42 | +var value = value / 1; | ||
43 | +function numberWithCommas(x) { | ||
44 | + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | ||
45 | +} | ||
46 | +return value ? numberWithCommas(value.toFixed(1)) : ''; | ||
47 | +{:copy-code} | ||
48 | +``` | ||
49 | + | ||
39 | <br> | 50 | <br> |
40 | <br> | 51 | <br> |
21.4 KB
36.6 KB