From e577b7f0f8c749bb203e30ff3a76e281a4acfb63 Mon Sep 17 00:00:00 2001 From: cstns Date: Mon, 15 Jul 2024 16:58:20 +0300 Subject: [PATCH 01/16] Expose the dataTracker composable to enable its use in third-party nodes --- docs/contributing/widgets/core-widgets.md | 2 +- ui/src/debug/Debug.vue | 2 +- ui/src/main.mjs | 2 ++ ui/src/widgets/ui-button-group/UIButtonGroup.vue | 6 ++---- ui/src/widgets/ui-button/UIButton.vue | 5 ++--- ui/src/widgets/ui-chart/UIChart.vue | 6 ++---- ui/src/widgets/ui-control/UIControl.vue | 2 +- ui/src/widgets/ui-dropdown/UIDropdown.vue | 6 ++---- ui/src/widgets/ui-event/UIEvent.vue | 5 ++--- ui/src/widgets/ui-file-input/UIFileInput.vue | 6 ++---- ui/src/widgets/ui-form/UIForm.vue | 6 ++---- ui/src/widgets/ui-gauge/UIGauge.vue | 5 ++--- ui/src/widgets/ui-gauge/types/UIGaugeDial.vue | 2 +- ui/src/widgets/ui-markdown/UIMarkdown.vue | 6 ++---- ui/src/widgets/ui-notification/UINotification.vue | 6 ++---- ui/src/widgets/ui-radio-group/UIRadioGroup.vue | 6 ++---- ui/src/widgets/ui-slider/UISlider.vue | 5 ++--- ui/src/widgets/ui-switch/UISwitch.vue | 5 ++--- ui/src/widgets/ui-table/UITable.vue | 5 ++--- ui/src/widgets/ui-template/UITemplate.vue | 5 +++-- ui/src/widgets/ui-text-input/UITextInput.vue | 6 ++---- ui/src/widgets/ui-text/UIText.vue | 5 ++--- 22 files changed, 41 insertions(+), 63 deletions(-) diff --git a/docs/contributing/widgets/core-widgets.md b/docs/contributing/widgets/core-widgets.md index 12ae23bc..68bf6230 100644 --- a/docs/contributing/widgets/core-widgets.md +++ b/docs/contributing/widgets/core-widgets.md @@ -52,7 +52,7 @@ When adding a new widget to Dashboard 2.0, you'll need to ensure that the follow export default { name: 'DBUIWidget', // we need to inject $socket so that we can send events to Node-RED - inject: ['$socket'], + inject: ['$socket', '$dt'], props: { id: String, // the id of the widget, as defined by Node-RED props: Object, // the properties for this widget defined in the Node-RED editor diff --git a/ui/src/debug/Debug.vue b/ui/src/debug/Debug.vue index fb4e7094..3b9bef96 100644 --- a/ui/src/debug/Debug.vue +++ b/ui/src/debug/Debug.vue @@ -186,7 +186,7 @@ export default { components: { 'debug-data': DebugData }, - inject: ['$socket'], + inject: ['$socket', '$dt'], data () { return { view: { diff --git a/ui/src/main.mjs b/ui/src/main.mjs index 871787cd..85bf7881 100644 --- a/ui/src/main.mjs +++ b/ui/src/main.mjs @@ -41,6 +41,7 @@ const theme = { error: '#ff5252' } } +import { useDataTracker } from './widgets/data-tracker.mjs' // eslint-disable-line import/order const vuetify = createVuetify({ components: { @@ -200,6 +201,7 @@ fetch('_setup') // make the socket service available app-wide via this.$socket app.provide('$socket', socket) + app.provide('$dt', useDataTracker) // mount the VueJS app into
in /ui/public/index.html app.mount('#app') diff --git a/ui/src/widgets/ui-button-group/UIButtonGroup.vue b/ui/src/widgets/ui-button-group/UIButtonGroup.vue index 5b966a8f..92df68d7 100644 --- a/ui/src/widgets/ui-button-group/UIButtonGroup.vue +++ b/ui/src/widgets/ui-button-group/UIButtonGroup.vue @@ -18,11 +18,9 @@ diff --git a/ui/src/widgets/ui-switch/UISwitch.vue b/ui/src/widgets/ui-switch/UISwitch.vue index d75b63d9..9ae281d3 100644 --- a/ui/src/widgets/ui-switch/UISwitch.vue +++ b/ui/src/widgets/ui-switch/UISwitch.vue @@ -17,9 +17,6 @@ export default { props: { type: Object, default: () => ({}) }, state: { type: Object, default: () => ({}) } }, - setup (props) { - this.$dataTracker(props.id) - }, data () { return { selection: null @@ -71,7 +68,7 @@ export default { }, created () { // can't do this in setup as we are using custom onInput function that needs access to 'this' - this.useDataTracker(this.id, this.onInput, this.onLoad, null) + this.$dataTracker(this.id, this.onInput, this.onLoad, null) // let Node-RED know that this widget has loaded this.$socket.emit('widget-load', this.id) diff --git a/ui/src/widgets/ui-table/UITable.vue b/ui/src/widgets/ui-table/UITable.vue index 7a4ef455..66546e0d 100644 --- a/ui/src/widgets/ui-table/UITable.vue +++ b/ui/src/widgets/ui-table/UITable.vue @@ -52,9 +52,6 @@ export default { id: { type: String, required: true }, props: { type: Object, default: () => ({}) } }, - setup (props) { - this.$dataTracker(props.id) - }, data () { return { selected: null, @@ -142,6 +139,9 @@ export default { } } }, + created () { + this.$dataTracker(this.id) + }, mounted () { this.calculatePaginatedRows() }, diff --git a/ui/src/widgets/ui-template/UITemplate.vue b/ui/src/widgets/ui-template/UITemplate.vue index d0eeb94a..bf14a9c1 100644 --- a/ui/src/widgets/ui-template/UITemplate.vue +++ b/ui/src/widgets/ui-template/UITemplate.vue @@ -76,12 +76,10 @@ export default { } } - this.$dataTracker(props.id) - // here we inject the UI Template Vue Template code into our own, in order to extend base functionality return () => h({ props: ['id', 'props'], - inject: ['$socket'], + inject: ['$socket', '$dataTracker'], errorCaptured: (err, vm, info) => { console.error('errorCaptured', err, vm, info) return false @@ -190,6 +188,9 @@ export default { props: props.props }) }, + created () { + this.$dataTracker(this.id) + }, errorCaptured: (err, vm, info) => { console.error('errorCaptured', err, vm, info) return false diff --git a/ui/src/widgets/ui-text/UIText.vue b/ui/src/widgets/ui-text/UIText.vue index 06ea227d..4f890a40 100644 --- a/ui/src/widgets/ui-text/UIText.vue +++ b/ui/src/widgets/ui-text/UIText.vue @@ -16,9 +16,6 @@ export default { id: { type: String, required: true }, props: { type: Object, default: () => ({}) } }, - setup (props) { - this.$dataTracker(props.id) - }, computed: { ...mapState('data', ['messages', 'properties']), value: function () { @@ -28,6 +25,9 @@ export default { } return '' } + }, + created () { + this.$dataTracker(this.id) } } From 72b699a61acc3d79853a917ab94ae3459d00b348 Mon Sep 17 00:00:00 2001 From: Joe Pavitt Date: Fri, 26 Jul 2024 18:37:23 +0100 Subject: [PATCH 14/16] Update Data Tracker documentation for Core Widgets --- docs/contributing/widgets/core-widgets.md | 31 ++++++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/contributing/widgets/core-widgets.md b/docs/contributing/widgets/core-widgets.md index c9086e78..38c2dcc3 100644 --- a/docs/contributing/widgets/core-widgets.md +++ b/docs/contributing/widgets/core-widgets.md @@ -87,21 +87,38 @@ When adding a new widget to Dashboard 2.0, you'll need to ensure that the follow ## Data Tracker -The data tracker is a set of utility functions that help setup the standard event handlers for a core widget. It will setup the following events: +The data tracker is a globally available utility service that helps setup the standard event handlers for widgets. -- `on('widget-load')` - to handle any initial data that is sent to the widget when it is loaded -- `on('msg-input')` - to handle any incoming data from Node-RED +### Usage -It also provides flexibility to define custom event handlers for the widget if there is bespoke functionality required for a given node, for example in a `ui-chart` node, we have a collection of custom logic that handles the merging of data points and the rendering of the chart when a message is received. +The data tracker is globally available across existing widgets and can be accessed using `this.$dataTracker(...)`. -The inputs for the `useDataTracker (widgetId, onInput, onLoad, onDynamicProperties)` function are used as follows: +The most simple usage of the tracker would be: + +```js +... +created () { + this.$dataTracker(this.id) +}, +... +``` + +This will setup the following events: + +- `on('widget-load')` - Ensures we save any received `msg` objects when a widget is first loaded into the Dashboard. +- `on('msg-input')` - Default behavior checks for any dynamic properties (e.g. visibility, disabled state) and also stores the incoming `msg` in the Vuex store + +### Custom Behaviours + +It also provides flexibility to define custom event handlers for a given widget, for example in a `ui-chart` node, we have a logic that handles the merging of data points and the rendering of the chart when a message is received. + +The inputs for the `this.$dataTracker(widgetId, onInput, onLoad, onDynamicProperties)` function are used as follows: - `widgetId` - the unique ID of the widget - `onInput` - a function that will be called when a message is received from Node-RED through the `on(msg-input)` socket handler - `onLoad` - a function that will be called when the widget is loaded, and triggered by the `widget-load` event -- `onDynamicProperties` - a function called as part of the `on(msg-input)` event, and is triggered _before_ the default `onInput` function. This is a good entry point to check against any properties that have been included in the `msg` in order to set a dynamic property. +- `onDynamicProperties` - a function called as part of the `on(msg-input)` event, and is triggered _before_ the default `onInput` function. This is a good entry point to check against any properties that have been included in the `msg` in order to set a dynamic property (i.e. content sent into `msg.ui_update...`). -The `useDataTracker` composable is globally available across existing widgets and can be accessed using `this.$dataTracker(...)`. ## Dynamic Properties From 28d9592c8a155cc9c38da9ddba5d16c709ea4533 Mon Sep 17 00:00:00 2001 From: Joe Pavitt Date: Fri, 26 Jul 2024 18:43:10 +0100 Subject: [PATCH 15/16] Update data tracker docs for third party widgets --- docs/contributing/widgets/third-party.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/contributing/widgets/third-party.md b/docs/contributing/widgets/third-party.md index f2c5da30..c5e3d8da 100644 --- a/docs/contributing/widgets/third-party.md +++ b/docs/contributing/widgets/third-party.md @@ -279,6 +279,23 @@ export default { } ``` +It is recommended to use our in built [Data Tracker](../widgets/core-widgets.md#data-tracker) to setup the standard input/load events for your widget. This can be done by calling the following in your widget's `.vue` file: + +```js +export default { + inject: ['$dataTracker'], + // rest of your vue component here + created () { + this.$dataTracker(this.id) + // we can override the default events if we want to with + // this.$dataTracker(this.id, myOnInputFunction, myOnLoadFunction, myOnDynamicPropertiesFunction) + } +} +``` + +More details on customisation of the Data Tracker can be found [here](../widgets/core-widgets.md#custom-behaviours). + + #### Sending Node-RED Messages You can send a `msg` on to any connected nodes in Node-RED by calling one of the following events via SocketIO: @@ -315,7 +332,6 @@ We use the concept of data stores on both the client and server side of Dashboar Data stores are a mapping of the widget/node's ID to the latest data received into that widget. This is most commonly used to restore state when the Dashboard is refreshed. -Any dataStore state changes can be intercepted using the `dataTracker` composable which is globally available in all existing widgets. More on the `dataTracker` composable [here](../widgets/core-widgets.md#data-tracker). #### Node-RED Data Store From fbf42ca43be43d5e67c5d0feae454c15d2d4e348 Mon Sep 17 00:00:00 2001 From: Joe Pavitt Date: Sun, 28 Jul 2024 16:49:43 +0100 Subject: [PATCH 16/16] Ensure example in docs is up-to-date --- docs/contributing/widgets/core-widgets.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/contributing/widgets/core-widgets.md b/docs/contributing/widgets/core-widgets.md index 38c2dcc3..d3c9532f 100644 --- a/docs/contributing/widgets/core-widgets.md +++ b/docs/contributing/widgets/core-widgets.md @@ -63,10 +63,9 @@ When adding a new widget to Dashboard 2.0, you'll need to ensure that the follow // received on input from Node-RED ...mapState('data', ['messages']), // provides access to `this.messages` where `this.messages[this.id]` is the stored msg for this widget }, - setup (props) { - // Use our data-tracker, which setups up the basic event handling for us - // including `on('msg-input')` and `on('widget-load')` - useDataTracker(props.id) + created () { + // setup the widget with default onInput, onLoad and onDynamicProperties handlers + this.$dataTracker(this.id) }, methods: { onAction () {