Skip to content

Commit

Permalink
Merge pull request #282 from FlowFuse/260-template-value
Browse files Browse the repository at this point in the history
Allow for simple data binding in ui-template
  • Loading branch information
joepavitt authored Oct 19, 2023
2 parents 26e67b2 + a636821 commit cf4e411
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 8 deletions.
32 changes: 27 additions & 5 deletions docs/nodes/widgets/ui-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,40 @@ props:

Provide custom HTML (including any [Vuetify components](https://vuetifyjs.com/en/components/all/)) to render in the Dashboard.

## Properties

<PropsTable/>

## Examples

![Examples of UI Template](/images/node-examples/ui-template.png "Examples of UI Template"){data-zoomable}
*Examples of variants of ui-template, one with a functional form, and another showing use of random Vuetify components.*

### Built In Functions
The template also has access to two built-in functions:

- `send` - Outputs a message (defined by the input to this function call) from this node in the Node-RED flow.
- `submit` - Send a `FormData` object when attached to a `<form>` element. The created object will consnist of the `name` attributes for each form element, corresponding to their respective `value` attributes.

_Note: Currently restricted to custom HTML only, but there are plans to add `<style>` and `<script>` editors in the future too (track [here](https://github.com/flowforge/flowforge-nr-dashboard/issues/115))._
### Binding variables to Custom Widgets

## Properties
If you want to `send` a value onwards from the `ui-template` node, that is defined by some input or action taken on your template, this is currently possible using an exposed variable on the `ui-template` named `value`.

<PropsTable/>
![Example of UI Template using Vuetify's Rating Widget](/images/node-examples/ui-template-rating1.png "Example of UI Template using Vuetify's Rating Widget"){data-zoomable}

Let's consider a use case where a user wants to add [Vuetify's star rating widget](https://vuetifyjs.com/en/components/ratings/):

```html
<v-rating hover :length="5" :size="32" v-model="value"
active-color="primary" @change="send({payload: value})"/>
```

Here, we've used Vue's `v-model` to create a two-way binding of the variable to whatever selection a user makes in the `v-rating` widget. Then, defined an `@change` event, such that when that value changes, we call our in-built `send(msg)` function, where we can make our `msg` on the fly in the function call itself.


When changed, if wired to a "Debug" node, then we can see the resulting outcome is as follows:

![Example output from using Vuetify's Rating Widget](/images/node-examples/ui-template-rating2.png "Example output from using Vuetify's Rating Widget"){data-zoomable}

## Example

![Examples of UI Template](/images/node-examples/ui-template.png "Examples of UI Template"){data-zoomable}
*Examples of variants of ui-template, one with a functional form, and another showing use of random Vuetify components.*
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion nodes/config/ui_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,11 @@ module.exports = function (RED) {
}
let msg = datastore.get(id) || {}
async function defaultHandler (value) {
msg.payload = value
if (typeof (value) === 'object' && value !== null && Object.hasOwn(value, 'payload')) {
msg.payload = value.payload
} else {
msg.payload = value
}

msg = await appendTopic(RED, widgetConfig, wNode, msg)

Expand Down
1 change: 1 addition & 0 deletions nodes/widgets/ui_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = function (RED) {

// which group are we rendering this widget
const group = RED.nodes.getNode(config.group)

const evts = {
onAction: true // TODO: think we need an onSend event for template nodes that matches up with a `widget-send` message
}
Expand Down
23 changes: 21 additions & 2 deletions ui/src/widgets/ui-template/UITemplate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,27 @@ export default {
...mapState('data', ['messages']),
msg () {
return this.messages[this.id] || {}
},
value: {
get () {
console.log('get value', this.id, this.messages[this.id]?.payload)
return this.messages[this.id]?.payload
},
set (val) {
if (this.value === val) {
return // no change
}
const msg = this.messages[this.id] || {}
msg.payload = val
this.messages[this.id] = msg
}
}
},
methods: {
send (msg) {
this.$parent.send(this, msg)
if (msg) {
this.$parent.send(this, msg)
}
},
submit ($evt) {
this.$parent.submit(this, $evt)
Expand Down Expand Up @@ -134,10 +150,13 @@ export default {
},
methods: {
send (component, msg) {
if (typeof (msg) !== 'object') {
msg = { payload: msg }
}
msg._dashboard = msg._dashboard || {}
msg._dashboard.sourceId = component.id
msg._dashboard.templateId = this.id
this.$socket.emit('widget-action', this.id, msg) // TODO: should we have a widget-send emitter to differentiate from action?
this.$socket.emit('widget-change', this.id, msg) // widget-change will store msg state server-side
},
submit (component, $evt) {
// extract the form names and values from $evt.target & generate a msg
Expand Down

0 comments on commit cf4e411

Please sign in to comment.