Skip to content

Commit

Permalink
Merge pull request #864 from FlowFuse/862-dynamic-props-button
Browse files Browse the repository at this point in the history
UI Button - Add dynamic properties for label, icon & iconPosition
  • Loading branch information
joepavitt authored May 30, 2024
2 parents 84c014e + a24b03f commit 9f3b4a8
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 15 deletions.
203 changes: 203 additions & 0 deletions cypress/fixtures/flows/dashboard-buttons.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
[
{
"id": "node-red-tab-buttons",
"type": "tab",
"label": "UI Buttons",
"disabled": false,
"info": "",
"env": []
},
{
"id": "test-helper",
"type": "function",
Expand Down Expand Up @@ -214,6 +222,201 @@
]
]
},
{
"id": "dashboard-ui-button-dynamic-label",
"type": "ui-button",
"z": "node-red-tab-buttons",
"group": "dashboard-ui-group",
"name": "Dynamic Property: Label",
"label": "Dynamic Property: Label",
"order": 0,
"width": 0,
"height": 0,
"emulateClick": false,
"tooltip": "",
"color": "",
"bgcolor": "",
"className": "",
"icon": "",
"iconPosition": "left",
"payload": "",
"payloadType": "str",
"topic": "topic",
"topicType": "msg",
"x": 190,
"y": 260,
"wires": [
[
"dashboard-ui-change-label"
]
]
},
{
"id": "dashboard-ui-change-label",
"type": "change",
"z": "node-red-tab-buttons",
"name": "",
"rules": [
{
"t": "set",
"p": "ui_update.label",
"pt": "msg",
"to": "Dynamic Button Label",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 410,
"y": 260,
"wires": [
[
"dashboard-ui-button-dynamic"
]
]
},
{
"id": "dashboard-ui-button-dynamic",
"type": "ui-button",
"z": "node-red-tab-buttons",
"group": "dashboard-ui-group",
"name": "Button Dynamic Properties",
"label": "Button Config Label",
"order": 0,
"width": 0,
"height": 0,
"emulateClick": false,
"tooltip": "",
"color": "",
"bgcolor": "",
"className": "",
"icon": "",
"iconPosition": "left",
"payload": "button 1 clicked",
"payloadType": "str",
"topic": "button-str-topic",
"topicType": "str",
"x": 640,
"y": 260,
"wires": [
[]
]
},
{
"id": "dashboard-ui-button-dynamic-icon",
"type": "ui-button",
"z": "node-red-tab-buttons",
"group": "dashboard-ui-group",
"name": "Dynamic Property: Icon",
"label": "Dynamic Property: Icon",
"order": 0,
"width": 0,
"height": 0,
"emulateClick": false,
"tooltip": "",
"color": "",
"bgcolor": "",
"className": "",
"icon": "",
"iconPosition": "left",
"payload": "",
"payloadType": "str",
"topic": "topic",
"topicType": "msg",
"x": 190,
"y": 300,
"wires": [
[
"dashboard-ui-change-icon"
]
]
},
{
"id": "dashboard-ui-change-icon",
"type": "change",
"z": "node-red-tab-buttons",
"name": "",
"rules": [
{
"t": "set",
"p": "ui_update.icon",
"pt": "msg",
"to": "earth",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 410,
"y": 300,
"wires": [
[
"dashboard-ui-button-dynamic"
]
]
},
{
"id": "dashboard-ui-button-dynamic-icon-pos",
"type": "ui-button",
"z": "node-red-tab-buttons",
"group": "dashboard-ui-group",
"name": "Dynamic Property: Icon Position",
"label": "Dynamic Property: Icon Position",
"order": 0,
"width": 0,
"height": 0,
"emulateClick": false,
"tooltip": "",
"color": "",
"bgcolor": "",
"className": "",
"icon": "",
"iconPosition": "left",
"payload": "",
"payloadType": "str",
"topic": "topic",
"topicType": "msg",
"x": 190,
"y": 340,
"wires": [
[
"dashboard-ui-change-icon-pos"
]
]
},
{
"id": "dashboard-ui-change-icon-pos",
"type": "change",
"z": "node-red-tab-buttons",
"name": "",
"rules": [
{
"t": "set",
"p": "ui_update.iconPosition",
"pt": "msg",
"to": "right",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 440,
"y": 340,
"wires": [
[
"dashboard-ui-button-dynamic"
]
]
},
{
"id": "dashboard-ui-group",
"type": "ui-group",
Expand Down
20 changes: 20 additions & 0 deletions cypress/tests/widgets/button.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,23 @@ describe('Node-RED Dashboard 2.0 - Buttons', () => {
cy.checkOutput('msg.payload', 'emulate')
})
})

describe('Node-RED Dashboard 2.0 - Buttons (Dynamic Properties)', () => {
beforeEach(() => {
cy.deployFixture('dashboard-buttons')
cy.visit('/dashboard/page1')
})

it('includes "label"', () => {
cy.get('#nrdb-ui-widget-dashboard-ui-button-dynamic').contains('Button Config Label')
cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-button-dynamic-label'))
cy.get('#nrdb-ui-widget-dashboard-ui-button-dynamic').contains('Dynamic Button Label')
})

it('includes "icon"', () => {
cy.get('#nrdb-ui-widget-dashboard-ui-button-dynamic').find('i.v-icon').should('not.exist')
cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-button-dynamic-icon'))
cy.get('#nrdb-ui-widget-dashboard-ui-button-dynamic').find('i.v-icon').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-button-dynamic').find('i.v-icon').should('have.class', 'mdi-earth')
})
})
23 changes: 19 additions & 4 deletions docs/nodes/widgets/ui-button.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,32 @@ description: "Buttons create interactive UI's and can trigger flows in Node-RED"
props:
Group: Defines which group of the UI Dashboard this widget will render in.
Size: Controls the width of the button with respect to the parent group. Maximum value is the width of the group.
Icon: Renders a Material Design icon within the button. There is no need to include the "mdi-" prefix.
Icon Position: If "Icon" is defined, this property controls which side of the "Label" the icon will render on.
Label: The text shown within the button. If not provided, then the button will only render the icon.
Icon:
description: Renders a Material Design icon within the button. There is no need to include the "mdi-" prefix.
dynamic: true
Icon Position:
description: If "Icon" is defined, this property controls which side of the "Label" the icon will render on.
dynamic: true
Label:
description: The text shown within the button. If not provided, then the button will only render the icon.
dynamic: true
Emulate Button Click: If enabled, any received message will trigger a button click, emitting the relevant payload and topic.
controls:
enabled:
example: true | false
description: Allow control over whether or not the button is clickable.
dynamic:
Icon:
payload: msg.ui_update.icon
structure: ["String"]
Icon Position:
payload: msg.ui_update.iconPosition
structure: ["String"]
Label:
payload: msg.ui_update.label
structure: ["String"]
Class:
payload: msg.class
payload: msg.ui_update.class
structure: ["String"]
---

Expand Down
5 changes: 5 additions & 0 deletions nodes/config/ui_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,11 @@ module.exports = function (RED) {
async function onLoad (conn, id, msg) {
// console.log('conn:' + conn.id, 'on:widget-load:' + id, msg)

if (!id) {
console.error('No widget id provided for widget-load event')
return
}

const { wNode, widgetEvents } = getWidgetAndConfig(id)
// any widgets we hard-code into our front end (e.g ui-notification for connection alerts) will start with ui-
// Node-RED built nodes will be a random UUID
Expand Down
8 changes: 7 additions & 1 deletion nodes/widgets/locales/en-US/ui_button.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ <h3>Properties</h3>
<dd>If enabled, any received message will trigger a button click, emitting the relevant payload and topic</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">label <span class="property-type">string</span></dt>
<dd>Override the label displayed on the button</dd>
<dt class="optional">icon <span class="property-type">string</span></dt>
<dd>Override the icon defined in the initial configuration</dd>
<dt class="optional">iconPosition <span class="property-type">'left' | 'right'</span></dt>
<dd>Change which side of the label the icon renders</dd>
<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>
Expand Down
19 changes: 19 additions & 0 deletions nodes/widgets/ui_button.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const statestore = require('../store/state.js')
const { appendTopic } = require('../utils/index.js')

module.exports = function (RED) {
Expand Down Expand Up @@ -45,6 +46,24 @@ module.exports = function (RED) {

msg.payload = payload

const updates = msg.ui_update

if (updates) {
// dynamic properties
if (typeof updates.label !== 'undefined') {
// dynamically set "label" property
statestore.set(group.getBase(), node, msg, 'label', updates.label)
}
if (typeof updates.icon !== 'undefined') {
// dynamically set "label" property
statestore.set(group.getBase(), node, msg, 'icon', updates.icon)
}
if (typeof updates.iconPosition !== 'undefined') {
// dynamically set "label" property
statestore.set(group.getBase(), node, msg, 'iconPosition', updates.iconPosition)
}
}

if (!error) {
return msg
} else {
Expand Down
6 changes: 5 additions & 1 deletion ui/src/widgets/data-tracker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { useStore } from 'vuex'

// by convention, composable function names start with "use"
export function useDataTracker (widgetId, onInput, onLoad, onDynamicProperties) {
if (!widgetId) {
throw new Error('widgetId is required')
}

const store = useStore()
const socket = inject('$socket')

Expand Down Expand Up @@ -37,7 +41,7 @@ export function useDataTracker (widgetId, onInput, onLoad, onDynamicProperties)
// a composable can also hook into its owner component's
// lifecycle to setup and teardown side effects.
onMounted(() => {
if (socket) {
if (socket && widgetId) {
socket.on('widget-load:' + widgetId, (msg) => {
if (onLoad) {
onLoad(msg)
Expand Down
Loading

0 comments on commit 9f3b4a8

Please sign in to comment.