diff --git a/docs/examples/group-dialog-type.json b/docs/examples/group-dialog-type.json
new file mode 100644
index 000000000..71e5fdc0f
--- /dev/null
+++ b/docs/examples/group-dialog-type.json
@@ -0,0 +1,215 @@
+[
+ {
+ "id": "0aa38a714b8a3d67",
+ "type": "ui-form",
+ "z": "9337c17e1a7f6875",
+ "name": "",
+ "group": "c7871ac53089d535",
+ "label": "New User",
+ "order": 1,
+ "width": 0,
+ "height": 0,
+ "options": [
+ {
+ "label": "Name",
+ "key": "name",
+ "type": "text",
+ "required": true,
+ "rows": null
+ },
+ {
+ "label": "Admin",
+ "key": "isAdmin",
+ "type": "switch",
+ "required": false,
+ "rows": null
+ }
+ ],
+ "formValue": {
+ "name": "",
+ "isAdmin": false
+ },
+ "payload": "",
+ "submit": "Add",
+ "cancel": "clear",
+ "resetOnSubmit": true,
+ "topic": "topic",
+ "topicType": "msg",
+ "splitLayout": "",
+ "className": "",
+ "passthru": false,
+ "dropdownOptions": [],
+ "x": 220,
+ "y": 100,
+ "wires": [
+ [
+ "adbd76ecf97076a1"
+ ]
+ ]
+ },
+ {
+ "id": "fa4925a5253341ce",
+ "type": "ui-control",
+ "z": "9337c17e1a7f6875",
+ "name": "",
+ "ui": "c2e1aa56f50f03bd",
+ "events": "all",
+ "x": 580,
+ "y": 100,
+ "wires": [
+ []
+ ]
+ },
+ {
+ "id": "adbd76ecf97076a1",
+ "type": "change",
+ "z": "9337c17e1a7f6875",
+ "name": "Hide Dialog",
+ "rules": [
+ {
+ "t": "set",
+ "p": "payload",
+ "pt": "msg",
+ "to": "{\"groups\":{\"hide\":[\"Active Development:Dialog Group\"]}}",
+ "tot": "json"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 390,
+ "y": 100,
+ "wires": [
+ [
+ "fa4925a5253341ce",
+ "b9d77a856b1be020"
+ ]
+ ]
+ },
+ {
+ "id": "294ac777d99f5789",
+ "type": "ui-button",
+ "z": "9337c17e1a7f6875",
+ "group": "497106faf38a5190",
+ "name": "",
+ "label": "Add User (Dialog)",
+ "order": 1,
+ "width": 0,
+ "height": 0,
+ "emulateClick": false,
+ "tooltip": "",
+ "color": "",
+ "bgcolor": "",
+ "className": "",
+ "icon": "",
+ "iconPosition": "left",
+ "payload": "{\"groups\":{\"show\":[\"Active Development:Dialog Group\"]}}",
+ "payloadType": "json",
+ "topic": "topic",
+ "topicType": "msg",
+ "buttonColor": "",
+ "textColor": "",
+ "iconColor": "",
+ "x": 370,
+ "y": 140,
+ "wires": [
+ [
+ "fa4925a5253341ce",
+ "b9d77a856b1be020"
+ ]
+ ]
+ },
+ {
+ "id": "b9d77a856b1be020",
+ "type": "debug",
+ "z": "9337c17e1a7f6875",
+ "name": "debug 2572",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "false",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 590,
+ "y": 140,
+ "wires": []
+ },
+ {
+ "id": "c7871ac53089d535",
+ "type": "ui-group",
+ "name": "Dialog Group",
+ "page": "22b5519aa675ad88",
+ "width": "6",
+ "height": "1",
+ "order": 1,
+ "showTitle": true,
+ "className": "",
+ "visible": "true",
+ "disabled": "false",
+ "groupType": "dialog"
+ },
+ {
+ "id": "c2e1aa56f50f03bd",
+ "type": "ui-base",
+ "name": "Dashboard",
+ "path": "/dashboard",
+ "includeClientData": true,
+ "acceptsClientConfig": [
+ "ui-control",
+ "ui-notification"
+ ],
+ "showPathInSidebar": false,
+ "showPageTitle": false,
+ "navigationStyle": "icon",
+ "titleBarStyle": "default"
+ },
+ {
+ "id": "497106faf38a5190",
+ "type": "ui-group",
+ "name": "Button Groups",
+ "page": "22b5519aa675ad88",
+ "width": "6",
+ "height": "1",
+ "order": 10,
+ "showTitle": true,
+ "className": "",
+ "visible": "true",
+ "disabled": "false"
+ },
+ {
+ "id": "22b5519aa675ad88",
+ "type": "ui-page",
+ "name": "Active Development",
+ "ui": "c2e1aa56f50f03bd",
+ "path": "/active-development",
+ "icon": "forum",
+ "layout": "grid",
+ "theme": "129e99574def90a3",
+ "order": 1,
+ "className": "",
+ "visible": "true",
+ "disabled": "false"
+ },
+ {
+ "id": "129e99574def90a3",
+ "type": "ui-theme",
+ "name": "Custom Theme",
+ "colors": {
+ "surface": "#000000",
+ "primary": "#ff4000",
+ "bgPage": "#f0f0f0",
+ "groupBg": "#ffffff",
+ "groupOutline": "#d9d9d9"
+ },
+ "sizes": {
+ "pagePadding": "24px",
+ "groupGap": "12px",
+ "groupBorderRadius": "9px",
+ "widgetGap": "6px",
+ "density": "default"
+ }
+ }
+]
\ No newline at end of file
diff --git a/docs/nodes/config/ui-group.md b/docs/nodes/config/ui-group.md
index 376277908..bb10d7682 100644
--- a/docs/nodes/config/ui-group.md
+++ b/docs/nodes/config/ui-group.md
@@ -2,13 +2,22 @@
description: Group your widgets effectively in Node-RED Dashboard 2.0 for better organization and user navigation.
props:
Name: Descriptive name for this group, will show in the Node-RED Editor and as a label in the Dashboard.
- Page: The Page (ui-page
) that this group will render on.
+ Page: The Page (ui-page
) that this group will render on.
+ Type: Controls whether the group appears as a default group or as a dialog, which needs to be triggered manually using ui-control. You can choose between 'Default' and 'Dialog' types.
Size: The width and height of the group. Height will always be reinforced by this value, the height is generally a minimum height, and will extend to fit it's content.
Class: Any custom CSS classes you wish to add to the Group.
Default State:
Visibility - Defines the default visibility of this group.Interactivity - Controls whether the group and it's contents are disabled/enabled when the page is loaded.Both of these can be overridden by the user at runtime using a ui-control
node.
---
# Config: UI Group `ui-group`
@@ -17,4 +26,20 @@ Each group is rendered within a `ui-page` as part of a [Layout](../../contributi
## Properties
-
\ No newline at end of file
+
+
+## Type
+
+Defines how the group will be displayed. Either as a regular (**Default**) group or as a **Dialog** group. A 'Default' group is visible by default, while a 'Dialog' group must be triggered manually using the `ui-control` node ([see documentation](/nodes/widgets/ui-control.html#show-hide)). You can choose between these two options based on your layout needs.
+
+### Default Groups
+
+![Example of how the type 'Default' option looks](/images/node-examples/ui-group-type-default.png "Example of how the type 'Default' option looks"){data-zoomable}
+_Example of how the type 'Default' option looks_
+
+### Dialog Groups
+
+![Example of how the type 'Dialog' option looks](/images/node-examples/ui-group-type-dialog.png "Example of how the type 'Dialog' option looks"){data-zoomable}
+_Example of how the type 'Dialog' option looks_
+
+
\ No newline at end of file
diff --git a/docs/public/images/node-examples/ui-group-type-default.png b/docs/public/images/node-examples/ui-group-type-default.png
new file mode 100644
index 000000000..252f9cd86
Binary files /dev/null and b/docs/public/images/node-examples/ui-group-type-default.png differ
diff --git a/docs/public/images/node-examples/ui-group-type-dialog.png b/docs/public/images/node-examples/ui-group-type-dialog.png
new file mode 100644
index 000000000..06f53769f
Binary files /dev/null and b/docs/public/images/node-examples/ui-group-type-dialog.png differ
diff --git a/nodes/config/locales/en-US/ui_group.html b/nodes/config/locales/en-US/ui_group.html
index 22e19c875..d50d848e7 100644
--- a/nodes/config/locales/en-US/ui_group.html
+++ b/nodes/config/locales/en-US/ui_group.html
@@ -1,6 +1,49 @@
diff --git a/nodes/config/locales/en-US/ui_group.json b/nodes/config/locales/en-US/ui_group.json
index 65332191d..c940211d7 100644
--- a/nodes/config/locales/en-US/ui_group.json
+++ b/nodes/config/locales/en-US/ui_group.json
@@ -14,7 +14,10 @@
"interactivity": "Interactivity",
"active": "Active",
"disabled": "Disabled",
- "openDashboardSidebar": "Open Dashboard 2.0 Sidebar"
+ "openDashboardSidebar": "Open Dashboard 2.0 Sidebar",
+ "type": "Type",
+ "typeDefault": "Default",
+ "typeDialog": "Dialog"
}
}
}
\ No newline at end of file
diff --git a/nodes/config/ui_base.html b/nodes/config/ui_base.html
index aa3953925..3de08dfea 100644
--- a/nodes/config/ui_base.html
+++ b/nodes/config/ui_base.html
@@ -989,7 +989,7 @@
const titleRow = $('', { class: 'nrdb2-sb-list-header nrdb2-sb-groups-list-header' }).appendTo(container)
$(' ').appendTo(titleRow)
const chevron = $('', { style: 'width:10px;' }).appendTo(titleRow)
- const groupicon = 'fa-table'
+ const groupicon = group.node.groupType === 'dialog' ? 'fa-window-restore' : 'fa-table'
$('', { class: 'nrdb2-sb-icon nrdb2-sb-group-icon fa ' + groupicon }).appendTo(titleRow)
$('', { class: 'nrdb2-sb-title' }).text(group.name || group.id).appendTo(titleRow)
$('', { class: 'nrdb2-sb-info' }).text(`${widgets.length} Widgets`).appendTo(titleRow)
diff --git a/nodes/config/ui_group.html b/nodes/config/ui_group.html
index eae33fb17..b7a20b6c3 100644
--- a/nodes/config/ui_group.html
+++ b/nodes/config/ui_group.html
@@ -13,7 +13,8 @@
showTitle: { value: true }, // show title on group card
className: { value: '' },
visible: { value: true },
- disabled: { value: false }
+ disabled: { value: false },
+ groupType: { value: 'default' }
},
label: function () {
const page = RED.nodes.node(this.page)?.name || ''
@@ -45,6 +46,8 @@
this.disabled = true
$('#node-config-input-disabled').val('true')
}
+
+ $('#node-config-input-groupType').val(this.groupType || 'default')
},
oneditsave: function () {
// convert string to boolean
@@ -75,6 +78,13 @@
+
+
+
+
+
+
+
diff --git a/ui/src/layouts/DialogGroup.vue b/ui/src/layouts/DialogGroup.vue
new file mode 100644
index 000000000..8a516087a
--- /dev/null
+++ b/ui/src/layouts/DialogGroup.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+ {{ group.name }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/layouts/Flex.vue b/ui/src/layouts/Flex.vue
index 6ef255057..cd97869e5 100644
--- a/ui/src/layouts/Flex.vue
+++ b/ui/src/layouts/Flex.vue
@@ -31,6 +31,21 @@
:state="widget.state"
/>
+
@@ -38,12 +53,14 @@
import { mapGetters, mapState } from 'vuex'
import BaselineLayout from './Baseline.vue'
+import DialogGroup from './DialogGroup.vue'
import WidgetGroup from './Group.vue'
export default {
name: 'LayoutFlex',
components: {
BaselineLayout,
+ DialogGroup,
WidgetGroup
},
data () {
@@ -62,7 +79,7 @@ export default {
// only show hte groups that haven't had their "visible" property set to false
.filter((g) => {
if ('visible' in g) {
- return g.visible
+ return g.visible && g.groupType !== 'dialog'
}
return true
})
@@ -71,6 +88,10 @@ export default {
})
return groups
},
+ dialogGroups () {
+ const groups = this.groupsByPage(this.$route.meta.id).filter((g) => g.groupType === 'dialog')
+ return groups
+ },
pageWidgets: function () {
return this.widgetsByPage(this.$route.meta.id)
},
diff --git a/ui/src/layouts/Grid.vue b/ui/src/layouts/Grid.vue
index caf3c3b1e..392827914 100644
--- a/ui/src/layouts/Grid.vue
+++ b/ui/src/layouts/Grid.vue
@@ -31,6 +31,21 @@
:state="widget.state"
/>
+
@@ -38,6 +53,7 @@
import Responsiveness from '../mixins/responsiveness.js'
import BaselineLayout from './Baseline.vue'
+import DialogGroup from './DialogGroup.vue'
import WidgetGroup from './Group.vue'
// eslint-disable-next-line import/order, sort-imports
@@ -47,6 +63,7 @@ export default {
name: 'LayoutGrid',
components: {
BaselineLayout,
+ DialogGroup,
WidgetGroup
},
mixins: [Responsiveness],
@@ -60,7 +77,7 @@ export default {
// only show hte groups that haven't had their "visible" property set to false
.filter((g) => {
if ('visible' in g) {
- return g.visible
+ return g.visible && g.groupType !== 'dialog'
}
return true
})
@@ -69,6 +86,10 @@ export default {
})
return groups
},
+ dialogGroups () {
+ const groups = this.groupsByPage(this.$route.meta.id).filter((g) => g.groupType === 'dialog')
+ return groups
+ },
pageWidgets: function () {
return this.widgetsByPage(this.$route.meta.id)
},
diff --git a/ui/src/layouts/Notebook.vue b/ui/src/layouts/Notebook.vue
index ee70c2ce0..e42ff0db7 100644
--- a/ui/src/layouts/Notebook.vue
+++ b/ui/src/layouts/Notebook.vue
@@ -30,6 +30,21 @@
:state="widget.state"
/>
+
@@ -39,12 +54,14 @@ import { mapGetters, mapState } from 'vuex'
import Responsiveness from '../mixins/responsiveness.js'
import BaselineLayout from './Baseline.vue'
+import DialogGroup from './DialogGroup.vue'
import WidgetGroup from './Group.vue'
export default {
name: 'LayoutFlex',
components: {
BaselineLayout,
+ DialogGroup,
WidgetGroup
},
mixins: [Responsiveness],
@@ -64,7 +81,7 @@ export default {
// only show the groups that haven't had their "visible" property set to false
.filter((g) => {
if ('visible' in g) {
- return g.visible
+ return g.visible && g.groupType !== 'dialog'
}
return true
})
@@ -73,6 +90,10 @@ export default {
})
return groups
},
+ dialogGroups () {
+ const groups = this.groupsByPage(this.$route.meta.id).filter((g) => g.groupType === 'dialog')
+ return groups
+ },
pageWidgets: function () {
return this.widgetsByPage(this.$route.meta.id)
},
diff --git a/ui/src/layouts/Tabs.vue b/ui/src/layouts/Tabs.vue
index 02e59c19a..224aafcb7 100644
--- a/ui/src/layouts/Tabs.vue
+++ b/ui/src/layouts/Tabs.vue
@@ -32,6 +32,21 @@
+
@@ -41,6 +56,7 @@ import Responsiveness from '../mixins/responsiveness.js'
// eslint-disable-next-line import/order
import BaselineLayout from './Baseline.vue'
+import DialogGroup from './DialogGroup.vue'
import WidgetGroup from './Group.vue'
// eslint-disable-next-line import/order, sort-imports
@@ -50,6 +66,7 @@ export default {
name: 'LayoutTabs',
components: {
BaselineLayout,
+ DialogGroup,
WidgetGroup
},
mixins: [Responsiveness],
@@ -68,7 +85,7 @@ export default {
// only show hte groups that haven't had their "visible" property set to false
.filter((g) => {
if ('visible' in g) {
- return g.visible
+ return g.visible && g.groupType !== 'dialog'
}
return true
})
@@ -77,6 +94,10 @@ export default {
})
return groups
},
+ dialogGroups () {
+ const groups = this.groupsByPage(this.$route.meta.id).filter((g) => g.groupType === 'dialog')
+ return groups
+ },
pageWidgets: function () {
return this.widgetsByPage(this.$route.meta.id)
},
diff --git a/ui/src/widgets/ui-control/UIControl.vue b/ui/src/widgets/ui-control/UIControl.vue
index 4a25c9872..b435c7622 100644
--- a/ui/src/widgets/ui-control/UIControl.vue
+++ b/ui/src/widgets/ui-control/UIControl.vue
@@ -174,15 +174,31 @@ export default {
if ('groups' in payload) {
if ('show' in payload.groups) {
- // we are setting visibility: true
payload.groups.show.forEach((name) => {
- setGroup(name, 'visible', true)
+ const groupName = name.split(':')[1]
+ const exactGroup = vue.groups ? Object.values(vue.groups).find(group => group.name === groupName) : null
+
+ if (exactGroup?.groupType === 'dialog') {
+ // we are setting dialog visibility: true
+ setGroup(name, 'showDialog', `true-${Date.now().toString()}`)
+ } else {
+ // we are setting visibility: true
+ setGroup(name, 'visible', true)
+ }
})
}
if ('hide' in payload.groups) {
- // we are setting visibility: false
payload.groups.hide.forEach((name) => {
- setGroup(name, 'visible', false)
+ const groupName = name.split(':')[1]
+ const exactGroup = vue.groups ? Object.values(vue.groups).find(group => group.name === groupName) : null
+
+ if (exactGroup?.groupType === 'dialog') {
+ // we are setting dialog visibiliry: false
+ setGroup(name, 'showDialog', `false-${Date.now().toString()}`)
+ } else {
+ // we are setting visibility: false
+ setGroup(name, 'visible', false)
+ }
})
}
if ('disable' in payload.groups) {