Skip to content

Commit

Permalink
Merge pull request #1214 from FlowFuse/1177-dynamic-properties-ui-tex…
Browse files Browse the repository at this point in the history
…t-input

Dynamic prop support for UI Text Input
  • Loading branch information
joepavitt authored Aug 29, 2024
2 parents c5e089d + 201ca01 commit 8cc3239
Show file tree
Hide file tree
Showing 9 changed files with 3,754 additions and 3,440 deletions.
516 changes: 516 additions & 0 deletions cypress/fixtures/flows/dashboard-text-input.json

Large diffs are not rendered by default.

30 changes: 0 additions & 30 deletions cypress/tests/widgets/number.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,4 @@ describe('Node/-RED Dashboard 2.0 - Number Input Widget', () => {
cy.get('#nrdb-ui-widget-2f48e919876213cc .nrdb-ui-number-field').trigger('mouseover')
cy.get('.v-tooltip').should('contain', 'Tooltip Text')
})

// Test case: Emits onClear event correctly
it('reset field on onClear event correctly and outputs the correct payload', () => {
cy.get('#nrdb-ui-widget-2f48e919876213cc input[type="text"]').clear()
cy.get('#nrdb-ui-widget-2f48e919876213cc input[type="text"]').type('2')
cy.get('#nrdb-ui-widget-2f48e919876213cc input[type="text"]').trigger('keydown', { key: 'Enter' })
cy.clickAndWait(cy.get('#nrdb-ui-widget-2f48e919876213cc .nrdb-ui-number-field .v-field__clearable'))
cy.get('#nrdb-ui-widget-2f48e919876213cc input[type="text"]').should('have.value', '')
cy.checkOutput('msg.payload', null)
})
})

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

it('Set the dynamic properties: set input "label"', () => {
cy.get('#nrdb-ui-widget-bc2a346d36830a3f').should('exist')
cy.clickAndWait(cy.get('button').contains('Dynamic Property: Label'))
cy.get('#nrdb-ui-widget-bc2a346d36830a3f .v-input .v-field .v-field__field').find('.v-field-label').should('exist')
})

it('Set the dynamic properties: set input "clearable"', () => {
cy.get('#nrdb-ui-widget-bc2a346d36830a3f').should('exist')
cy.clickAndWait(cy.get('button').contains('Dynamic Property: Clearable'))
cy.get('#nrdb-ui-widget-bc2a346d36830a3f').type('4')
cy.get('#nrdb-ui-widget-bc2a346d36830a3f .v-input .v-field .v-field__field').find('.v-field-label').should('exist')
})
})
18 changes: 18 additions & 0 deletions cypress/tests/widgets/text-input.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-disable cypress/no-unnecessary-waiting */
describe('Node/-RED Dashboard 2.0 - Text Input Widget', () => {
beforeEach(() => {
cy.deployFixture('dashboard-text-input')
cy.visit('/dashboard/page1')
})

// Test case: Renders the Text Input widget correctly
it('renders the Text Input widget correctly', () => {
cy.get('#nrdb-ui-widget-ab3346b81a7cf742 input[type="text"]').should('exist')
})

// Test case: Displays the tooltip correctly
it('displays the tooltip correctly', () => {
cy.get('#nrdb-ui-widget-ab3346b81a7cf742 .nrdb-ui-text-field').trigger('mouseover')
cy.get('.v-tooltip').should('contain', 'Tooltip Text')
})
})
9 changes: 9 additions & 0 deletions docs/components/DynamicPropsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<th>Prop</th>
<th>Payload</th>
<th>Structures</th>
<th>Example Values</th>
</tr>
</thead>
<tbody>
Expand All @@ -19,6 +20,14 @@
</ul>
<code v-else>{{ value.structure[0] }}</code>
</td>
<td>
<div v-if="value.examples" style="display: flex; gap: 4px; align-items: center;">
<template v-for="(example, i) in value.examples" :key="example">
<code >{{ example }}</code>
<span v-if="i !== value.examples?.length - 1">|</span>
</template>
</div>
</td>
</tr>
</tbody>
</table>
Expand Down
44 changes: 38 additions & 6 deletions docs/nodes/widgets/ui-text-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,28 @@ description: Incorporate ui-text-input in Node-RED Dashboard 2.0 for customizabl
props:
Group: Defines which group of the UI Dashboard this widget will render in.
Size: Controls the width of the text input field with respect to the parent group. Maximum value is the width of the group.
Icon: Renders a Material Design icon within the Text Input. 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.
Icon Inner Position: If "Icon" is defined, this property controls if icon is render inside or outside the text input box
Label: The text shown within the text input field.
Icon:
description: Renders a Material Design icon within the Text Input. 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
Icon Inner Position:
description: If "Icon" is defined, this property controls if icon is render inside or outside the text input box.
dynamic: true
Label:
description: The text shown within the text input field.
dynamic: true
Tooltip: The text shown when hovering over the text input field.
Mode: The type of HTML input to display. Options - text | password | email | number | tel | color | date | time | week | month | datetime-local
Mode:
description: The type of HTML input to display. Options - text | password | email | number | tel | color | date | time | week | month | datetime-local
dynamic: true
Passthrough: If this node receives a msg in Node-RED, should it be passed through to the output as if a new value was inserted to the input?
Send On "Delay": If true, then a msg will be emitted will be sent after the delay specified in "Delay (ms)".
Delay: If "Send on Delay" is true, then the value in the text input will be send after this (ms) delay.
Clear selection with button: If true, a clear icon/button appears on the rigth side to clear the text input
Clear selection with button:
description: If true, a clear icon/button appears on the rigth side to clear the text input
dynamic: true
Send On "Focus Leave": Sends a msg when the text input loses focus. Will always send, even if the value has not changed.
Send On "Press Enter": Sends a msg when the user presses the enter key. Will always send, even if the value has not changed.
Send On "Clear Button": Send a msg when the user clear the text input using the clear button, the "Clear Selection" button must be enabled.
Expand All @@ -24,6 +36,26 @@ dynamic:
Class:
payload: msg.class
structure: ["String"]
Mode:
payload: msg.ui_update.mode
structure: ["String"]
Label:
payload: msg.ui_update.label
structure: ["String"]
Icon:
payload: msg.ui_update.icon
structure: ["String"]
Icon Position:
payload: msg.ui_update.iconPosition
structure: ["String"]
examples: ["left", "right"]
Icon Inner Position:
payload: msg.ui_update.iconInnerPosition
structure: ["String"]
examples: ["inside", "outside"]
Clearable:
payload: msg.ui_update.clearable
structure: ["Boolean"]
---

<script setup>
Expand Down
18 changes: 17 additions & 1 deletion nodes/widgets/locales/en-US/ui_text_input.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,25 @@
</ul>
</p>
<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>
<dl class="message-properties">
<dt class="optional">mode <span class="property-type">string</span></dt>
<dd>Change the text input to a "password", "color", or any of the other available "modes".</dd>
<dt class="optional">label <span class="property-type">string</span></dt>
<dd>Override the label displayed with the Text Input.</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 text-input the icon renders</dd>
<dt class="optional">iconInnerPosition <span class="property-type">'inside' | 'outside'</span></dt>
<dd>Defines whether or not the icon renders within the frame of the text-input.</dd>
<dt class="optional">clearable <span class="property-type">boolean</span></dt>
<dd>Controls whether an "x" button appears as a user types in order to quickly clear any written content.</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>
</script>
38 changes: 32 additions & 6 deletions nodes/widgets/ui_text_input.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
const datastore = require('../store/data.js')
const statestore = require('../store/state.js')

module.exports = function (RED) {
function TextInputNode (config) {
const node = this

// create node in Node-RED
RED.nodes.createNode(this, config)

// this ndoe need to store content/value from UI
node.value = null
const node = this

// which group are we rendering this widget
const group = RED.nodes.getNode(config.group)

const evts = {
onChange: true,
beforeSend: async function (msg) {
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.mode !== 'undefined') {
// dynamically set "mode" property
statestore.set(group.getBase(), node, msg, 'mode', updates.mode)
}
if (typeof updates.clearable !== 'undefined') {
// dynamically set "clearable" property
statestore.set(group.getBase(), node, msg, 'clearable', updates.clearable)
}
if (typeof updates.icon !== 'undefined') {
// dynamically set "icon" property
statestore.set(group.getBase(), node, msg, 'icon', updates.icon)
}
if (typeof updates.iconPosition !== 'undefined') {
// dynamically set "icon position" property
statestore.set(group.getBase(), node, msg, 'iconPosition', updates.iconPosition)
}
if (typeof updates.iconInnerPosition !== 'undefined') {
// dynamically set "icon inner position" property
statestore.set(group.getBase(), node, msg, 'iconInnerPosition', updates.iconInnerPosition)
}
}
return msg
},
onInput: function (msg, send) {
// store the latest msg passed to node
datastore.save(group.getBase(), node, msg)
Expand Down
Loading

0 comments on commit 8cc3239

Please sign in to comment.