@flowfuse/node-red-dashboard 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +53 -0
- package/dist/css/app.d047b42b.css +1 -0
- package/dist/css/chunk-vendors.2378ce49.css +24 -0
- package/dist/fonts/materialdesignicons-webfont.3de8526e.woff +0 -0
- package/dist/fonts/materialdesignicons-webfont.477c6ab0.woff2 +0 -0
- package/dist/fonts/materialdesignicons-webfont.48a1ce0c.eot +0 -0
- package/dist/fonts/materialdesignicons-webfont.dfd403cf.ttf +0 -0
- package/dist/index.html +1 -0
- package/dist/js/app.854a8cd5.js +2 -0
- package/dist/js/app.854a8cd5.js.map +1 -0
- package/dist/js/chunk-vendors.174e8921.js +43 -0
- package/dist/js/chunk-vendors.174e8921.js.map +1 -0
- package/nodes/config/locales/en-US/ui_base.json +19 -0
- package/nodes/config/locales/en-US/ui_group.html +4 -0
- package/nodes/config/locales/en-US/ui_group.json +16 -0
- package/nodes/config/ui_base.html +807 -0
- package/nodes/config/ui_base.js +678 -0
- package/nodes/config/ui_group.html +55 -0
- package/nodes/config/ui_group.js +34 -0
- package/nodes/config/ui_page.html +84 -0
- package/nodes/config/ui_page.js +33 -0
- package/nodes/config/ui_theme.html +101 -0
- package/nodes/config/ui_theme.js +15 -0
- package/nodes/store/index.js +34 -0
- package/nodes/utils/index.js +35 -0
- package/nodes/widgets/locales/en-US/ui_button.html +7 -0
- package/nodes/widgets/locales/en-US/ui_button.json +24 -0
- package/nodes/widgets/locales/en-US/ui_chart.html +41 -0
- package/nodes/widgets/locales/en-US/ui_chart.json +17 -0
- package/nodes/widgets/locales/en-US/ui_dropdown.html +24 -0
- package/nodes/widgets/locales/en-US/ui_form.html +16 -0
- package/nodes/widgets/locales/en-US/ui_form.json +36 -0
- package/nodes/widgets/locales/en-US/ui_markdown.html +10 -0
- package/nodes/widgets/locales/en-US/ui_slider.html +9 -0
- package/nodes/widgets/locales/en-US/ui_switch.html +32 -0
- package/nodes/widgets/locales/en-US/ui_template.html +59 -0
- package/nodes/widgets/locales/en-US/ui_template.json +18 -0
- package/nodes/widgets/locales/en-US/ui_text.html +16 -0
- package/nodes/widgets/locales/en-US/ui_text_input.html +19 -0
- package/nodes/widgets/ui_button.html +146 -0
- package/nodes/widgets/ui_button.js +65 -0
- package/nodes/widgets/ui_chart.html +314 -0
- package/nodes/widgets/ui_chart.js +195 -0
- package/nodes/widgets/ui_dropdown.html +199 -0
- package/nodes/widgets/ui_dropdown.js +19 -0
- package/nodes/widgets/ui_form.html +368 -0
- package/nodes/widgets/ui_form.js +18 -0
- package/nodes/widgets/ui_markdown.html +134 -0
- package/nodes/widgets/ui_markdown.js +14 -0
- package/nodes/widgets/ui_notification.html +139 -0
- package/nodes/widgets/ui_notification.js +14 -0
- package/nodes/widgets/ui_radio_group.html +186 -0
- package/nodes/widgets/ui_radio_group.js +20 -0
- package/nodes/widgets/ui_slider.html +162 -0
- package/nodes/widgets/ui_slider.js +31 -0
- package/nodes/widgets/ui_switch.html +194 -0
- package/nodes/widgets/ui_switch.js +98 -0
- package/nodes/widgets/ui_table.html +149 -0
- package/nodes/widgets/ui_table.js +16 -0
- package/nodes/widgets/ui_template.html +283 -0
- package/nodes/widgets/ui_template.js +19 -0
- package/nodes/widgets/ui_text.html +358 -0
- package/nodes/widgets/ui_text.js +98 -0
- package/nodes/widgets/ui_text_input.html +141 -0
- package/nodes/widgets/ui_text_input.js +37 -0
- package/package.json +114 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
const datastore = require('../store/index.js')
|
|
2
|
+
|
|
3
|
+
module.exports = function (RED) {
|
|
4
|
+
function ChartNode (config) {
|
|
5
|
+
const node = this
|
|
6
|
+
|
|
7
|
+
// create node in Node-RED
|
|
8
|
+
RED.nodes.createNode(this, config)
|
|
9
|
+
|
|
10
|
+
// which group are we rendering this widget
|
|
11
|
+
const group = RED.nodes.getNode(config.group)
|
|
12
|
+
|
|
13
|
+
function getProperty (value, property) {
|
|
14
|
+
const props = property.split('.')
|
|
15
|
+
props.forEach((prop) => {
|
|
16
|
+
if (value) {
|
|
17
|
+
value = value[prop]
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
return value
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const evts = {
|
|
24
|
+
beforeSend: function (msg) {
|
|
25
|
+
const p = msg.payload
|
|
26
|
+
|
|
27
|
+
let series = RED.util.evaluateNodeProperty(config.category, config.categoryType, node, msg)
|
|
28
|
+
// if receiving a object payload, the series could be a within the payload
|
|
29
|
+
if (config.categoryType === 'property') {
|
|
30
|
+
series = getProperty(p, config.category)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (config.chartType === 'line' || config.chartType === 'scatter') {
|
|
34
|
+
// possible that we haven't received any x-data in the payload,
|
|
35
|
+
// so let's make sure we append something
|
|
36
|
+
|
|
37
|
+
// single point or array of data?
|
|
38
|
+
if (Array.isArray(p)) {
|
|
39
|
+
// array of data
|
|
40
|
+
msg._datapoint = p.map((point) => {
|
|
41
|
+
// series available on a msg by msg basis - ensure we check for each msg
|
|
42
|
+
if (config.categoryType === 'property') {
|
|
43
|
+
series = getProperty(point, config.category)
|
|
44
|
+
}
|
|
45
|
+
return addToLine(point, series)
|
|
46
|
+
})
|
|
47
|
+
} else {
|
|
48
|
+
// single point
|
|
49
|
+
msg._datapoint = addToLine(p, series)
|
|
50
|
+
}
|
|
51
|
+
} else if (config.chartType === 'bar') {
|
|
52
|
+
// single point or array of data?
|
|
53
|
+
if (Array.isArray(p)) {
|
|
54
|
+
// array of data
|
|
55
|
+
msg._datapoint = p.map((point) => {
|
|
56
|
+
if (config.categoryType === 'property') {
|
|
57
|
+
series = getProperty(point, config.category)
|
|
58
|
+
}
|
|
59
|
+
return addToBar(point, series)
|
|
60
|
+
})
|
|
61
|
+
} else {
|
|
62
|
+
// single point
|
|
63
|
+
msg._datapoint = addToBar(p, series)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// function to process a data point being appended to a line/scatter chart
|
|
68
|
+
function addToLine (payload, series) {
|
|
69
|
+
const datapoint = {}
|
|
70
|
+
datapoint.category = series
|
|
71
|
+
// construct our datapoint
|
|
72
|
+
if (typeof payload === 'number') {
|
|
73
|
+
// just a number, assume we're plotting a time series
|
|
74
|
+
datapoint.x = (new Date()).getTime()
|
|
75
|
+
datapoint.y = payload
|
|
76
|
+
} else if (typeof payload === 'object') {
|
|
77
|
+
// may have been given an x/y object already
|
|
78
|
+
let x = getProperty(payload, config.xAxisProperty)
|
|
79
|
+
if (x === undefined || x === null) {
|
|
80
|
+
x = (new Date()).getTime()
|
|
81
|
+
}
|
|
82
|
+
datapoint.x = x
|
|
83
|
+
datapoint.y = payload.y
|
|
84
|
+
}
|
|
85
|
+
return datapoint
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// the only server-side computed var we need is the category for a Bar Chart
|
|
89
|
+
function addToBar (payload, series) {
|
|
90
|
+
const datapoint = {}
|
|
91
|
+
datapoint.category = series
|
|
92
|
+
if (typeof payload === 'number') {
|
|
93
|
+
datapoint.y = payload
|
|
94
|
+
}
|
|
95
|
+
return datapoint
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return msg
|
|
99
|
+
},
|
|
100
|
+
onInput: function (msg, send, done) {
|
|
101
|
+
// use our own custom onInput in order to store history of msg payloads
|
|
102
|
+
if (!datastore.get(node.id)) {
|
|
103
|
+
datastore.save(node.id, [])
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(msg.payload) && !msg.payload.length) {
|
|
106
|
+
// clear history
|
|
107
|
+
datastore.save(node.id, [])
|
|
108
|
+
} else {
|
|
109
|
+
if (!Array.isArray(msg.payload)) {
|
|
110
|
+
// quick clone of msg, and store in history
|
|
111
|
+
datastore.append(node.id, {
|
|
112
|
+
...msg
|
|
113
|
+
})
|
|
114
|
+
} else {
|
|
115
|
+
// we have an array in msg.payload, let's split them
|
|
116
|
+
msg.payload.forEach((p, i) => {
|
|
117
|
+
const payload = JSON.parse(JSON.stringify(p))
|
|
118
|
+
const d = msg._datapoint ? msg._datapoint[i] : null
|
|
119
|
+
const m = {
|
|
120
|
+
...msg,
|
|
121
|
+
payload,
|
|
122
|
+
_datapoint: d
|
|
123
|
+
}
|
|
124
|
+
datastore.append(node.id, m)
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const maxPoints = parseInt(config.removeOlderPoints)
|
|
129
|
+
|
|
130
|
+
if (config.xAxisType === 'category') {
|
|
131
|
+
const _msg = datastore.get(node.id)
|
|
132
|
+
|
|
133
|
+
// filters the ._msg array so that we keep just the latest msg with each category/series
|
|
134
|
+
const seen = {}
|
|
135
|
+
_msg.forEach((m, index) => {
|
|
136
|
+
const series = m._datapoint.category
|
|
137
|
+
// loop through and record the latest index seen for each topic/label
|
|
138
|
+
seen[series] = index
|
|
139
|
+
})
|
|
140
|
+
const indices = Object.values(seen)
|
|
141
|
+
datastore.save(node.id, _msg.filter((msg, index) => {
|
|
142
|
+
// return only the msgs with the latest index for each topic/label
|
|
143
|
+
return indices.includes(index)
|
|
144
|
+
}))
|
|
145
|
+
console.log(datastore.get(node.id))
|
|
146
|
+
} else if (maxPoints && config.removeOlderPoints) {
|
|
147
|
+
// account for multiple lines?
|
|
148
|
+
// client-side does this for _each_ line
|
|
149
|
+
// remove older points
|
|
150
|
+
const lineCounts = {}
|
|
151
|
+
const _msg = datastore.get(node.id)
|
|
152
|
+
// trawl through in reverse order, and only keep the latest points (up to maxPoints) for each label
|
|
153
|
+
for (let i = _msg.length - 1; i >= 0; i--) {
|
|
154
|
+
const msg = _msg[i]
|
|
155
|
+
const label = msg.topic
|
|
156
|
+
lineCounts[label] = lineCounts[label] || 0
|
|
157
|
+
if (lineCounts[label] >= maxPoints) {
|
|
158
|
+
_msg.splice(i, 1)
|
|
159
|
+
} else {
|
|
160
|
+
lineCounts[label]++
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
datastore.save(node.id, _msg)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (config.xAxisType === 'time' && config.removeOlder && config.removeOlderUnit) {
|
|
167
|
+
// remove any points older than the specified time
|
|
168
|
+
const removeOlder = parseFloat(config.removeOlder)
|
|
169
|
+
const removeOlderUnit = parseFloat(config.removeOlderUnit)
|
|
170
|
+
const ago = (removeOlder * removeOlderUnit) * 1000 // milliseconds ago
|
|
171
|
+
const cutoff = (new Date()).getTime() - ago
|
|
172
|
+
const _msg = datastore.get(node.id).filter((msg) => {
|
|
173
|
+
let timestamp = msg._datapoint.x
|
|
174
|
+
// is x already a millisecond timestamp?
|
|
175
|
+
if (typeof (msg._datapoint.x) === 'string') {
|
|
176
|
+
timestamp = (new Date(msg._datapoint.x)).getTime()
|
|
177
|
+
}
|
|
178
|
+
return timestamp > cutoff
|
|
179
|
+
})
|
|
180
|
+
datastore.save(node.id, _msg)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// check sizing limits
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
send(msg)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// inform the dashboard UI that we are adding this node
|
|
191
|
+
group.register(node, config, evts)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
RED.nodes.registerType('ui-chart', ChartNode)
|
|
195
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
(function () {
|
|
3
|
+
function hasProperty (obj, prop) {
|
|
4
|
+
return Object.prototype.hasOwnProperty.call(obj, prop)
|
|
5
|
+
}
|
|
6
|
+
RED.nodes.registerType('ui-dropdown', {
|
|
7
|
+
category: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.label.category'),
|
|
8
|
+
color: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.colors.light'),
|
|
9
|
+
defaults: {
|
|
10
|
+
group: { type: 'ui-group', required: true },
|
|
11
|
+
name: { value: '' },
|
|
12
|
+
label: { value: 'Select Option:' },
|
|
13
|
+
tooltip: { value: '' },
|
|
14
|
+
order: { value: 0 },
|
|
15
|
+
width: {
|
|
16
|
+
value: 0,
|
|
17
|
+
validate: function (v) {
|
|
18
|
+
const width = v || 0
|
|
19
|
+
const currentGroup = $('#node-input-group').val() || this.group
|
|
20
|
+
const groupNode = RED.nodes.node(currentGroup)
|
|
21
|
+
const valid = !groupNode || +width <= +groupNode.width
|
|
22
|
+
$('#node-input-size').toggleClass('input-error', !valid)
|
|
23
|
+
return valid
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
height: { value: 0 },
|
|
27
|
+
passthru: { value: false },
|
|
28
|
+
multiple: { value: false },
|
|
29
|
+
options: {
|
|
30
|
+
value: [{ value: '', label: '' }],
|
|
31
|
+
validate: function (v) {
|
|
32
|
+
const unique = new Set(v.map(function (o) { return o.value }))
|
|
33
|
+
return v.length === unique.size
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
payload: { value: '' },
|
|
37
|
+
topic: { value: 'topic', validate: (hasProperty(RED.validators, 'typedInput') ? RED.validators.typedInput('topicType') : function (v) { return true }) },
|
|
38
|
+
topicType: { value: 'msg' },
|
|
39
|
+
className: { value: '' }
|
|
40
|
+
},
|
|
41
|
+
inputs: 1,
|
|
42
|
+
outputs: 1,
|
|
43
|
+
icon: 'font-awesome/fa-bars',
|
|
44
|
+
paletteLabel: 'dropdown',
|
|
45
|
+
label: function () { return this.name || (~this.label.indexOf('{' + '{') ? null : this.label) || 'dropdown' },
|
|
46
|
+
labelStyle: function () { return this.name ? 'node_label_italic' : '' },
|
|
47
|
+
oneditprepare: function () {
|
|
48
|
+
if (this.multiple === undefined) {
|
|
49
|
+
$('#node-input-multiple').prop('checked', false)
|
|
50
|
+
}
|
|
51
|
+
$('#node-input-size').elementSizer({
|
|
52
|
+
width: '#node-input-width',
|
|
53
|
+
height: '#node-input-height',
|
|
54
|
+
group: '#node-input-group'
|
|
55
|
+
})
|
|
56
|
+
const un = new Set(this.options.map(function (o) { return o.value }))
|
|
57
|
+
if (this.options.length === un.size) { $('#valWarning').hide() } else { $('#valWarning').show() }
|
|
58
|
+
|
|
59
|
+
function generateOption (i, option) {
|
|
60
|
+
const container = $('<li/>', { style: 'background: var(--red-ui-secondary-background, #fff); margin:0; padding:8px 0px 0px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc);' })
|
|
61
|
+
const row = $('<div/>').appendTo(container)
|
|
62
|
+
$('<div/>', { style: 'padding-top:5px; padding-left:175px;' }).appendTo(container)
|
|
63
|
+
$('<div/>', { style: 'padding-top:5px; padding-left:120px;' }).appendTo(container)
|
|
64
|
+
|
|
65
|
+
$('<i style="color: var(--red-ui-form-text-color, #eee); cursor:move; margin-left:3px;" class="node-input-option-handle fa fa-bars"></i>').appendTo(row)
|
|
66
|
+
|
|
67
|
+
$('<input/>', { class: 'node-input-option-value', type: 'text', style: 'margin-left:7px; width:calc(50% - 32px);', placeholder: 'Value', value: option.value }).appendTo(row).typedInput({ default: option.type || 'str', types: ['str', 'num', 'bool'] })
|
|
68
|
+
$('<input/>', { class: 'node-input-option-label', type: 'text', style: 'margin-left:7px; width:calc(50% - 32px);', placeholder: 'Label', value: option.label }).appendTo(row)
|
|
69
|
+
|
|
70
|
+
const finalSpan = $('<span/>', { style: 'float:right; margin-right:8px;' }).appendTo(row)
|
|
71
|
+
const deleteButton = $('<a/>', { href: '#', class: 'editor-button editor-button-small', style: 'margin-top:7px; margin-left:5px;' }).appendTo(finalSpan)
|
|
72
|
+
$('<i/>', { class: 'fa fa-remove' }).appendTo(deleteButton)
|
|
73
|
+
|
|
74
|
+
deleteButton.click(function () {
|
|
75
|
+
container.css({ background: 'var(--red-ui-secondary-background-inactive, #fee)' })
|
|
76
|
+
container.fadeOut(300, function () {
|
|
77
|
+
$(this).remove()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
$('#node-input-option-container').append(container)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
$('#node-input-add-option').click(function () {
|
|
85
|
+
generateOption($('#node-input-option-container').children().length + 1, {})
|
|
86
|
+
$('#node-input-option-container-div').scrollTop($('#node-input-option-container-div').get(0).scrollHeight)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < this.options.length; i++) {
|
|
90
|
+
const option = this.options[i]
|
|
91
|
+
generateOption(i + 1, option)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
$('#node-input-option-container').sortable({
|
|
95
|
+
axis: 'y',
|
|
96
|
+
handle: '.node-input-option-handle',
|
|
97
|
+
cursor: 'move'
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
$('#node-input-topic').typedInput({
|
|
101
|
+
default: 'str',
|
|
102
|
+
typeField: $('#node-input-topicType'),
|
|
103
|
+
types: ['str', 'msg', 'flow', 'global']
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// use jQuery UI tooltip to convert the plain old title attribute to a nice tooltip
|
|
107
|
+
$('.ui-node-popover-title').tooltip({
|
|
108
|
+
show: {
|
|
109
|
+
effect: 'slideDown',
|
|
110
|
+
delay: 150
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
},
|
|
114
|
+
oneditsave: function () {
|
|
115
|
+
const options = $('#node-input-option-container').children()
|
|
116
|
+
const node = this
|
|
117
|
+
node.options = []
|
|
118
|
+
options.each(function (i) {
|
|
119
|
+
const option = $(this)
|
|
120
|
+
const o = {
|
|
121
|
+
label: option.find('.node-input-option-label').val(),
|
|
122
|
+
value: option.find('.node-input-option-value').typedInput('value'),
|
|
123
|
+
type: option.find('.node-input-option-value').typedInput('type')
|
|
124
|
+
}
|
|
125
|
+
if (option.find('.node-input-option-value').typedInput('type') === 'num') {
|
|
126
|
+
o.value = Number(o.value)
|
|
127
|
+
}
|
|
128
|
+
if (option.find('.node-input-option-value').typedInput('type') === 'bool') {
|
|
129
|
+
o.value = (o.value === 'true')
|
|
130
|
+
}
|
|
131
|
+
node.options.push(o)
|
|
132
|
+
})
|
|
133
|
+
},
|
|
134
|
+
oneditresize: function () {
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
})()
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<script type="text/html" data-template-name="ui-dropdown">
|
|
141
|
+
<div class="form-row">
|
|
142
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
143
|
+
<input type="text" id="node-input-name">
|
|
144
|
+
</div>
|
|
145
|
+
<div class="form-row">
|
|
146
|
+
<label for="node-input-group"><i class="fa fa-table"></i> Group</label>
|
|
147
|
+
<input type="text" id="node-input-group">
|
|
148
|
+
</div>
|
|
149
|
+
<div class="form-row">
|
|
150
|
+
<label><i class="fa fa-object-group"></i> Size</label>
|
|
151
|
+
<input type="hidden" id="node-input-width">
|
|
152
|
+
<input type="hidden" id="node-input-height">
|
|
153
|
+
<button class="editor-button" id="node-input-size"></button>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="form-row">
|
|
156
|
+
<label for="node-input-label"><i class="fa fa-tag"></i> Label</label>
|
|
157
|
+
<input type="text" id="node-input-label" placeholder="optional label">
|
|
158
|
+
</div>
|
|
159
|
+
<div class="form-row">
|
|
160
|
+
<label for="node-input-className"><i class="fa fa-code"></i> Class</label>
|
|
161
|
+
<div style="display: inline;">
|
|
162
|
+
<input style="width: 70%;" type="text" id="node-input-className" placeholder="Optional CSS class name(s)" style="flex-grow: 1;">
|
|
163
|
+
<a
|
|
164
|
+
data-html="true"
|
|
165
|
+
title="Dynamic Property: Class names can also be set by sending a message to the node with a msg.topic of 'ui-property:class' and a payload containing the class name(s) to be applied. NOTE: classes set at runtime will be applied in addition to any class(es) set in the nodes class field."
|
|
166
|
+
class="red-ui-button ui-node-popover-title"
|
|
167
|
+
style="margin-left: 4px; cursor: help; font-size: 0.625rem; border-radius: 50%; width: 24px; height: 24px; display: inline-flex; justify-content: center; align-items: center;">
|
|
168
|
+
<i style="font-family: ui-serif;">fx</i>
|
|
169
|
+
</a>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
<!--<div class="form-row">
|
|
173
|
+
<label for="node-input-tooltip"><i class="fa fa-info-circle"></i> Tooltip</label>
|
|
174
|
+
<input type="text" id="node-input-tooltip" placeholder="optional tooltip">
|
|
175
|
+
</div>-->
|
|
176
|
+
<div class="form-row node-input-option-container-row" style="margin-bottom: 0px;width: 100%">
|
|
177
|
+
<label for="node-input-width" style="vertical-align:top"><i class="fa fa-list-alt"></i> Options</label>
|
|
178
|
+
<div id="node-input-option-container-div" style="box-sizing:border-box; border-radius:5px; height:257px; padding:5px; border:1px solid var(--red-ui-form-input-border-color, #ccc); overflow-y:scroll; display:inline-block; width:calc(70% + 15px);">
|
|
179
|
+
<span id="valWarning" style="color: var(--red-ui-text-color-error, #910000)"><b>All Values must be unique.</b></span>
|
|
180
|
+
<ol id="node-input-option-container" style="list-style-type:none; margin:0;"></ol>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="form-row">
|
|
184
|
+
<a href="#" class="editor-button editor-button-small" id="node-input-add-option" style="margin-top:4px; margin-left:103px;"><i class="fa fa-plus"></i> <span>option</span></a>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="form-row">
|
|
187
|
+
<label style="width:auto" for="node-input-multiple"><i class="fa fa-th-list"></i> Allow multiple selections from list: </label>
|
|
188
|
+
<input type="checkbox" checked id="node-input-multiple" style="display: inline-block; width: auto; margin: 0px 0px 0px 4px;">
|
|
189
|
+
</div>
|
|
190
|
+
<div class="form-row">
|
|
191
|
+
<label style="width:auto" for="node-input-passthru"><i class="fa fa-arrow-right"></i> If <code>msg</code> arrives on input, pass through to output: </label>
|
|
192
|
+
<input type="checkbox" checked id="node-input-passthru" style="display:inline-block; width:auto; vertical-align:top;">
|
|
193
|
+
</div>
|
|
194
|
+
<div class="form-row">
|
|
195
|
+
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
|
196
|
+
<input type="text" id="node-input-topic" style="width:70%" placeholder="optional msg.topic">
|
|
197
|
+
<input type="hidden" id="node-input-topicType">
|
|
198
|
+
</div>
|
|
199
|
+
</script>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
function DropdownNode (config) {
|
|
3
|
+
// create node in Node-RED
|
|
4
|
+
RED.nodes.createNode(this, config)
|
|
5
|
+
const node = this
|
|
6
|
+
|
|
7
|
+
// which group are we rendering this widget
|
|
8
|
+
const group = RED.nodes.getNode(config.group)
|
|
9
|
+
|
|
10
|
+
const evts = {
|
|
11
|
+
onChange: true
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// inform the dashboard UI that we are adding this node
|
|
15
|
+
group.register(node, config, evts)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
RED.nodes.registerType('ui-dropdown', DropdownNode)
|
|
19
|
+
}
|