Secondary Tab Group(s) with ui-dropdown do not populate msg.ui_update.options if they are not the active/visible group
Current Behavior
When I have a dashboard using the Tab Layout, I am dynamically populating various ui-dropdown nodes with options across multiple groups. The ui-dropdown node within the active tab are populated with the options when the page is viewed using a ui-event node. I can confirm that messages are passed to each ui-dropdown using debug nodes, but no options are populated.
Expected Behavior
I would expect all ui-dropdowns on separate tab groups to populate with options, so that when a separate tab group is accessed, the options are available.
Steps To Reproduce
Configure a page with tab layout, add multiple tab groups, and place a ui-dropdown node on each group. Dynamically provide options to each ui-dropdown node using msg.ui_update.options, originating from a ui-event node.
Open the dashboard page with tab groups, navigate to a secondary group, and confirm that no options are populated.
Sample flow code: [ { "id": "e8e12134c6e25677", "type": "ui-dropdown", "z": "44d110eda5c2622b", "group": "4cf27e96a0500a25", "name": "Drop-Down 1", "label": "Select Option:", "tooltip": "", "order": 1, "width": 0, "height": 0, "passthru": false, "multiple": false, "chips": false, "clearable": false, "options": [ { "label": "", "value": "", "type": "str" } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", "typeIsComboBox": true, "msgTrigger": "onChange", "x": 860, "y": 840, "wires": [ [] ] }, { "id": "7042d7873f6cd7e3", "type": "ui-event", "z": "44d110eda5c2622b", "ui": "400f2675d972ee11", "name": "", "x": 240, "y": 880, "wires": [ [ "c43b94542a49c22a" ] ] }, { "id": "c43b94542a49c22a", "type": "switch", "z": "44d110eda5c2622b", "name": "Tab Test Page?", "property": "payload.page.name = \"Tab Test\" and topic = \"$pageview\"", "propertyType": "jsonata", "rules": [ { "t": "true" } ], "checkall": "true", "repair": false, "outputs": 1, "x": 400, "y": 880, "wires": [ [ "98b343e3db216ec0" ] ] }, { "id": "98b343e3db216ec0", "type": "change", "z": "44d110eda5c2622b", "name": "", "rules": [ { "t": "set", "p": "ui_update.options", "pt": "msg", "to": "[\"A\",\"B\",\"C\"]", "tot": "json" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 620, "y": 880, "wires": [ [ "e8e12134c6e25677", "885caa0eea15d991", "979d0d4950d09bdc" ] ] }, { "id": "885caa0eea15d991", "type": "ui-dropdown", "z": "44d110eda5c2622b", "group": "3c9bfa7efc2644f1", "name": "Drop-Down 2", "label": "Select Option:", "tooltip": "", "order": 1, "width": 0, "height": 0, "passthru": false, "multiple": false, "chips": false, "clearable": false, "options": [ { "label": "", "value": "", "type": "str" } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", "typeIsComboBox": true, "msgTrigger": "onChange", "x": 860, "y": 880, "wires": [ [] ] }, { "id": "979d0d4950d09bdc", "type": "ui-dropdown", "z": "44d110eda5c2622b", "group": "d4ccab1b08ad44e9", "name": "Drop-Down 3", "label": "Select Option:", "tooltip": "", "order": 1, "width": 0, "height": 0, "passthru": false, "multiple": false, "chips": false, "clearable": false, "options": [ { "label": "", "value": "", "type": "str" } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", "typeIsComboBox": true, "msgTrigger": "onChange", "x": 860, "y": 920, "wires": [ [] ] }, { "id": "4cf27e96a0500a25", "type": "ui-group", "name": "Tab 1", "page": "2f89576d1fcf7164", "width": "6", "height": "1", "order": 1, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "400f2675d972ee11", "type": "ui-base", "name": "Dashboard", "path": "/dashboard", "includeClientData": true, "acceptsClientConfig": [ "ui-notification", "ui-control", "ui-template", "ui-button", "ui-text-input", "ui-radio-group", "ui-markdown", "ui-audio", "ui-gauge", "ui-chart", "ui-table", "ui-text", "ui-switch", "ui-slider", "ui-button-group", "ui-file-input", "ui-number-input", "ui-form", "ui-dropdown" ], "showPathInSidebar": false, "navigationStyle": "default", "titleBarStyle": "default" }, { "id": "3c9bfa7efc2644f1", "type": "ui-group", "name": "Tab 2", "page": "2f89576d1fcf7164", "width": "6", "height": "1", "order": 2, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "d4ccab1b08ad44e9", "type": "ui-group", "name": "Tab 3", "page": "2f89576d1fcf7164", "width": "6", "height": "1", "order": 3, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "2f89576d1fcf7164", "type": "ui-page", "name": "Tab Test", "ui": "400f2675d972ee11", "path": "/test", "icon": "home", "layout": "tabs", "theme": "6aed771fda3270a1", "breakpoints": [ { "name": "Default", "px": "0", "cols": "3" }, { "name": "Tablet", "px": "576", "cols": "6" }, { "name": "Small Desktop", "px": "768", "cols": "9" }, { "name": "Desktop", "px": "1024", "cols": "12" } ], "order": 2, "className": "", "visible": "true", "disabled": "false" }, { "id": "6aed771fda3270a1", "type": "ui-theme", "name": "Global Theme", "colors": { "surface": "#212121", "primary": "#2aace2", "bgPage": "#121212", "groupBg": "#212121", "groupOutline": "#585858" }, "sizes": { "pagePadding": "12px", "groupGap": "12px", "groupBorderRadius": "4px", "widgetGap": "12px", "density": "default" } } ]
Environment
- Dashboard version: v1.22.1
- Node-RED version: 3.1.15
- Node.js version: 18.20.5
- npm version: 10.8.2
- Platform/OS: FlowFuse v2.14.1, Node-RED Launcher v2.14.1-3.1.x on Kubernetes/Linux AMD x86_64
- Browser: Confirmed on both Firefox Version 137.0 (ARM 64-bit) as well as Chrome Version 135.0.7049.42 (Official Build) (arm64)
Have you provided an initial effort estimate for this issue?
I am not a FlowFuse team member
This bug was reported by https://app-eu1.hubspot.com/contacts/26586079/record/0-1/26913651
@joepavitt could you comment when you have some time please?
Thanks Rob, I'm out until Friday next week, but @Steve-Mcl could take a look before that
@Steve-Mcl can you investigate this please? I think there may be a fundamental architectural gap here with our rendering of the widgets when switching tabs, they should be retrieving the server-side state on mounted. That server-side state should have the latest options in it.
It's likely to be one of:
- It's not reading from server-side when mounted
- The event handler for the dynamic update is updating the server side state, but not the client side because it's not running the active event listeners. If it's mounted, it should be doing this. If it's not mounted, it should be getting server-side state when it is mounted anyway.
- The server-side state is not getting updated at all, but this is unlikely as options persists on a refresh
We have had a request for status from the customer on this item - would be good to get it looked at as a priority next week.
@Steve-Mcl This one passed 2.17, but let's keep moving forward on this one.
Just worked through this with @Steve-Mcl - it's not straight forward, but I will do my best to articulate:
- The issue arises from when and how we store "state" on a widget. When you send a
ui_updaterequest, we (a) send it to the client, where any actively rendred widgets receive it and action it, and (b) store the state server-side in our "state store". When a widget then first rendered in the UI, we have awidget-loadevent which reads the "state" from the store, and displays the updated options. This works correctly. - However, we only do this for widgets that have a shared state across all client connections. If the "UI Dropdown" is toggled on in the "Client Constraints" tab for Dashboard (such that each dropdown has unique state per user), then this message is only sent to the front-end connection specified in
msg._client(which UI Event includes), and is not stored server-side. This means that the client-side widgets not yet rendered, e.g. dropdowns on other tabs, do not receive the request to update their options, nor is it stored server-side.
Workarounds:
- Make the
optionsuniversal across all client connections, you can do this in one of two ways:- Un-toggle
ui-dropdownfrom the "Client Constraints" tab in the Dashboard sidebar. This will mean that all dropdowns share state across all client/user connections. - Add a
changenode in after theui-eventnode, andDeletethemsg._clientobject. This will mean that only this property and dropdown will share state across all client connections
- Un-toggle
- We introduce a new
$tabchangeevent that is emitted by UI Event. This will fire the request to dynamically populate the options (and the same for any other widget in question) which gives the coverage needed.
The latter workaround is imo the correct fix here as it provides coverage across all of the user cases
I have set this to "backlog" in dashboard tracker and to "todo" in flowfuse tracker.
Thanks for the follow up! We do require user context for the drop-downs in other spots so I’ve updated the flow to remove msg._client when the drop-downs are populated. The only detriment here is that if user 2 loads the page after user 1 has selected some of the items in the drop down, user 1’s drop-down selection is cleared. I don’t have a lot of items in these particular drop-downs and this scenario might be rare, but could cause some confusion. Should be okay for now.
Regardless I agree the best way to handle this would be on the tab change event, so it’s good to see that this feature is being worked on.
This would be valuable to https://app-eu1.hubspot.com/contacts/26586079/record/0-1/26913651