Skip to content

Commit

Permalink
Merge pull request #1227 from bartbutenaers/switch-dynamic-props
Browse files Browse the repository at this point in the history
switch dynamic properties
  • Loading branch information
joepavitt authored Aug 27, 2024
2 parents f0c9ea0 + d8c144a commit 95f83e6
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 47 deletions.
23 changes: 22 additions & 1 deletion docs/nodes/widgets/ui-switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,28 @@ controls:
description: Allow control over whether or not the switch can be toggled via the UI.
dynamic:
Class:
payload: msg.class
payload: msg.ui_update.class
structure: ["String"]
Label:
payload: msg.ui_update.label
structure: ["Boolean"]
Passthrough:
payload: msg.ui_update.passthru
structure: ["Boolean"]
Indicator:
payload: msg.ui_update.decouple
structure: ["Boolean"]
On Color:
payload: msg.ui_update.oncolor
structure: ["String"]
Off Color:
payload: msg.ui_update.offcolor
structure: ["String"]
On Icon:
payload: msg.ui_update.onicon
structure: ["String"]
Off Icon:
payload: msg.ui_update.officon
structure: ["String"]
---

Expand Down
32 changes: 30 additions & 2 deletions nodes/widgets/locales/en-US/ui_switch.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,37 @@ <h3>Properties</h3>
<dd>The type & value to output in msg.payload when the switch is turned off.</dd>
</dl>
<h3>Dynamic Properties (Inputs)</h3>
<p>Any of the following can be appended to a <code>msg.</code> in order to override or set properties on this node at runtime.</p>
<p>Any of the following can be appended to a <code>msg.ui_update</code> in order to override or set properties on this node at runtime.</p>
<dl class="message-properties">
<dt class="optional">class <span class="property-type">string</span></dt>
<dd>Add a CSS class, or more, to the Button at runtime.</dd>
</dl>
</script>
<dl class="message-properties">
<dt class="optional">label <span class="property-type">string</span></dt>
<dd>Change the switch label at runtime.</dd>
</dl>
<dl class="message-properties">
<dt class="optional">passthru <span class="property-type">boolean</span></dt>
<dd>Change the passthrough behaviour of input messages at runtime.</dd>
</dl>
<dl class="message-properties">
<dt class="optional">decouple <span class="property-type">boolean</span></dt>
<dd>Change the indicator at runtime. When <code>true</code> the icon shows state of the input, and when <code>false</code> the state of the output</dd>
</dl>
<dl class="message-properties">
<dt class="optional">oncolor <span class="property-type">string</span></dt>
<dd>Change the ON color of the switch at runtime.</dd>
</dl>
<dl class="message-properties">
<dt class="optional">offcolor <span class="property-type">string</span></dt>
<dd>Change the OFF color of the switch at runtime.</dd>
</dl>
<dl class="message-properties">
<dt class="optional">onicon <span class="property-type">string</span></dt>
<dd>Change the ON icon of the switch at runtime.</dd>
</dl>
<dl class="message-properties">
<dt class="optional">officon <span class="property-type">string</span></dt>
<dd>Change the OFF icon of the switch at runtime.</dd>
</dl>
</script>
69 changes: 44 additions & 25 deletions nodes/widgets/ui_switch.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const datastore = require('../store/data.js')
const statestore = require('../store/state.js')
const { appendTopic } = require('../utils/index.js')

module.exports = function (RED) {
Expand All @@ -14,24 +15,27 @@ module.exports = function (RED) {
// which group are we rendering this widget
const group = RED.nodes.getNode(config.group)

// retrieve the assigned on/off values
const on = RED.util.evaluateNodeProperty(config.onvalue, config.onvalueType, node)
const off = RED.util.evaluateNodeProperty(config.offvalue, config.offvalueType, node)

config.evaluated = {
on,
off
}

const evts = {
// runs on UI interaction
// value = true | false from the ui-switch
onChange: async function (msg, value) {
// ensure we have latest instance of the widget's node
const wNode = RED.nodes.getNode(node.id)

// retrieve the assigned on/off value
const on = RED.util.evaluateNodeProperty(config.onvalue, config.onvalueType, wNode)
const off = RED.util.evaluateNodeProperty(config.offvalue, config.offvalueType, wNode)
msg.payload = value ? on : off

if (config.topic || config.topicType) {
msg = await appendTopic(RED, config, node, msg)
}

if (!config.passthru && config.decouple) {
wNode.send(msg)
node.send(msg)
} else {
node.status({
fill: value ? 'green' : 'red',
Expand All @@ -41,17 +45,11 @@ module.exports = function (RED) {
datastore.save(group.getBase(), node, msg)

// simulate Node-RED node receiving an input
wNode.send(msg)
node.send(msg)
}
},
onInput: async function (msg, send) {
let error = null
// ensure we have latest instance of the widget's node
const wNode = RED.nodes.getNode(node.id)

// retrieve the assigned on/off value
const on = RED.util.evaluateNodeProperty(config.onvalue, config.onvalueType, wNode)
const off = RED.util.evaluateNodeProperty(config.offvalue, config.offvalueType, wNode)

if (msg.payload === undefined) {
// may be setting class dynamically or something else that doesn't require a payload
Expand Down Expand Up @@ -100,22 +98,43 @@ module.exports = function (RED) {
}
},
beforeSend: async function (msg) {
// ensure we have latest instance of the widget's node
const wNode = RED.nodes.getNode(node.id)
const updates = msg.ui_update
if (updates) {
if (typeof updates.label !== 'undefined') {
// dynamically set "label" property
statestore.set(group.getBase(), node, msg, 'label', updates.label)
}
if (typeof updates.passthru !== 'undefined') {
// dynamically set "passthru" property
statestore.set(group.getBase(), node, msg, 'passthru', updates.passthru)
}
if (typeof updates.decouple !== 'undefined') {
// dynamically set "decouple" property
statestore.set(group.getBase(), node, msg, 'decouple', updates.decouple)
}
if (typeof updates.oncolor !== 'undefined') {
// dynamically set "oncolor" property
statestore.set(group.getBase(), node, msg, 'oncolor', updates.oncolor)
}
if (typeof updates.offcolor !== 'undefined') {
// dynamically set "offcolor" property
statestore.set(group.getBase(), node, msg, 'offcolor', updates.offcolor)
}
if (typeof updates.onicon !== 'undefined') {
// dynamically set "onicon" property
statestore.set(group.getBase(), node, msg, 'onicon', updates.onicon)
}
if (typeof updates.officon !== 'undefined') {
// dynamically set "officon" property
statestore.set(group.getBase(), node, msg, 'officon', updates.officon)
}
}

msg = await appendTopic(RED, config, wNode, msg)
msg = await appendTopic(RED, config, node, msg)
return msg
}
}

const on = RED.util.evaluateNodeProperty(config.onvalue, config.onvalueType, node)
const off = RED.util.evaluateNodeProperty(config.offvalue, config.offvalueType, node)

config.evaluated = {
on,
off
}

// inform the dashboard UI that we are adding this node
group.register(node, config, evts)
}
Expand Down
61 changes: 42 additions & 19 deletions ui/src/widgets/ui-switch/UISwitch.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<template>
<div class="nrdb-switch" :class="{'nrdb-nolabel': !props.label, [className]: !!className}">
<label v-if="props.label" class="v-label">{{ props.label }}</label>
<div class="nrdb-switch" :class="{'nrdb-nolabel': !label, [className]: !!className}">
<label v-if="label" class="v-label">{{ label }}</label>
<v-switch
v-if="!icon" v-model="status"
:disabled="!state.enabled" :class="{'active': status}"
:disabled="!state.enabled"
:class="{'active': status}"
hide-details="auto" color="primary"
:loading="loading ? (status === true ? 'secondary' : 'primary') : null"
readonly
Expand Down Expand Up @@ -33,21 +34,30 @@ export default {
},
computed: {
...mapState('data', ['messages']),
icon: function () {
if (this.props.onicon && this.props.officon) {
const icon = this.status ? this.props.onicon : this.props.officon
label () {
return this.getProperty('label')
},
icon () {
const onicon = this.getProperty('onicon')
const officon = this.getProperty('officon')
if (onicon && officon) {
const icon = this.status ? onicon : officon
return 'mdi-' + icon.replace(/^mdi-/, '')
} else {
return null
}
},
color: function () {
if (this.props.oncolor || this.props.offcolor) {
return this.status ? this.props.oncolor : this.props.offcolor
color () {
const oncolor = this.getProperty('oncolor')
const offcolor = this.getProperty('offcolor')
if (oncolor || offcolor) {
return this.status ? oncolor : offcolor
}
return null
},
value: function () {
value () {
return this.selection
},
status: {
Expand All @@ -72,28 +82,41 @@ export default {
msg.payload = val
this.selection = val
this.messages[this.id] = msg
if (this.decouple) {
if (this.getProperty('decouple')) {
this.loading = true
}
}
}
},
created () {
// can't do this in setup as we are using custom onInput function that needs access to 'this'
this.$dataTracker(this.id, this.onInput, this.onLoad, null)
this.$dataTracker(this.id, this.onInput, this.onLoad, this.onDynamicProperties)
// let Node-RED know that this widget has loaded
this.$socket.emit('widget-load', this.id)
},
methods: {
onDynamicProperties (msg) {
const updates = msg.ui_update
if (!updates) {
return
}
this.updateDynamicProperty('label', updates.label)
this.updateDynamicProperty('decouple', updates.decouple)
this.updateDynamicProperty('oncolor', updates.oncolor)
this.updateDynamicProperty('offcolor', updates.offcolor)
this.updateDynamicProperty('onicon', updates.onicon)
this.updateDynamicProperty('officon', updates.officon)
},
onInput (msg) {
// update our vuex store with the value retrieved from Node-RED
this.$store.commit('data/bind', {
widgetId: this.id,
msg
})
// make sure our v-model is updated to reflect the value from Node-RED
// Update our vuex store with the value (in the payload) retrieved from Node-RED.
if (msg.payload !== undefined) {
this.$store.commit('data/bind', {
widgetId: this.id,
msg
})
// make sure our v-model is updated to reflect the value from Node-RED
this.selection = msg.payload
this.loading = false
}
Expand All @@ -113,7 +136,7 @@ export default {
},
toggle () {
if (this.state.enabled) {
if (this.props.decouple) {
if (this.getProperty('decouple')) {
this.loading = true
// send the inverse, but don't update the status until we get a response
this.$socket.emit('widget-change', this.id, !this.status)
Expand Down

0 comments on commit 95f83e6

Please sign in to comment.