diff --git a/cypress/fixtures/flows/dashboard-slider.json b/cypress/fixtures/flows/dashboard-slider.json index 55bd8398..e6e9dc23 100644 --- a/cypress/fixtures/flows/dashboard-slider.json +++ b/cypress/fixtures/flows/dashboard-slider.json @@ -329,5 +329,149 @@ "dashboard-ui-slider" ] ] + }, + { + "id": "dashboard-ui-button-dynamic-textfield", + "type": "ui-button", + "z": "node-red-tab-slider", + "group": "dashboard-ui-group", + "name": "Show Text Field", + "label": "Show Text Field", + "order": 0, + "width": 0, + "height": 0, + "emulateClick": false, + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "iconPosition": "left", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "buttonColor": "", + "textColor": "", + "iconColor": "", + "enableClick": true, + "enablePointerdown": false, + "pointerdownPayload": "", + "pointerdownPayloadType": "str", + "enablePointerup": false, + "pointerupPayload": "", + "pointerupPayloadType": "str", + "x": 180, + "y": 280, + "wires": [ + [ + "dashboard-ui-set-property-textfield" + ] + ] + }, + { + "id": "dashboard-ui-set-property-textfield", + "type": "change", + "z": "node-red-tab-slider", + "name": "", + "rules": [ + { + "t": "delete", + "p": "payload", + "pt": "msg" + }, + { + "t": "delete", + "p": "topic", + "pt": "msg" + }, + { + "t": "set", + "p": "ui_update.showTextField", + "pt": "msg", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 380, + "y": 280, + "wires": [ + [ + "dashboard-ui-slider" + ] + ] + }, + { + "id": "dashboard-ui-set-property-step", + "type": "change", + "z": "node-red-tab-slider", + "name": "", + "rules": [ + { + "t": "delete", + "p": "payload", + "pt": "msg" + }, + { + "t": "delete", + "p": "topic", + "pt": "msg" + }, + { + "t": "set", + "p": "msg.ui_update.step", + "pt": "msg", + "to": "2", + "tot": "num" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 380, + "y": 320, + "wires": [["dashboard-ui-slider"]] + }, + { + "id": "dashboard-ui-button-dynamic-step", + "type": "ui-button", + "z": "node-red-tab-slider", + "group": "dashboard-ui-group", + "name": "Set Step", + "label": "Set Step", + "order": 0, + "width": 0, + "height": 0, + "emulateClick": false, + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "iconPosition": "left", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "buttonColor": "", + "textColor": "", + "iconColor": "", + "enableClick": true, + "enablePointerdown": false, + "pointerdownPayload": "", + "pointerdownPayloadType": "str", + "enablePointerup": false, + "pointerupPayload": "", + "pointerupPayloadType": "str", + "x": 160, + "y": 320, + "wires": [["dashboard-ui-set-property-step"]] } ] \ No newline at end of file diff --git a/cypress/tests/widgets/slider.spec.js b/cypress/tests/widgets/slider.spec.js index e9a98db9..08a5290f 100644 --- a/cypress/tests/widgets/slider.spec.js +++ b/cypress/tests/widgets/slider.spec.js @@ -47,4 +47,63 @@ describe('Node-RED Dashboard 2.0 - Slider (Dynamic Properties)', () => { // check the min value is updated cy.get('#nrdb-ui-widget-dashboard-ui-slider').find('.v-slider-thumb').should('have.attr', 'aria-valuemax', '50') }) + it('include "showTextField"', () => { + // First, check that the text field does not exist + cy.get('#nrdb-ui-widget-dashboard-ui-slider').within(() => { cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').should('not.exist') }) + cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-button-dynamic-textfield')) + // Check if the text field is present when showTextField is true + cy.get('#nrdb-ui-widget-dashboard-ui-slider').within(() => { cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').should('exist') }) + }) +}) + +describe('Node-RED Dashboard 2.0 - Slider (Text Field Input)', () => { + beforeEach(() => { + cy.deployFixture('dashboard-slider') + cy.visit('/dashboard/page1') + }) + + it('text field emits a value on blur', () => { + cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field'), 200) + // then we can type into the input + cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').type('{selectall}{del}40') + cy.focused().blur() + cy.checkOutput('msg.payload', 40) + }) + + it('text field validation for max value', () => { + // Extract the max value from the slider thumb and store it in a variable + // eslint-disable-next-line promise/always-return, promise/catch-or-return + cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').invoke('attr', 'max').then((maxValue) => { + const greaterThanMaxValue = parseInt(maxValue) + 10 + cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field'), 200) + // then we can type into the input + cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').type(`{selectall}{del}${greaterThanMaxValue}`) + cy.focused().blur() + cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').should('have.value', maxValue) + }) + }) + it('text field validation for min value', () => { + // Extract the min value from the slider thumb and store it in a variable + // eslint-disable-next-line promise/catch-or-return, promise/always-return + cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').invoke('attr', 'min').then((minValue) => { + const lessThanMinValue = parseInt(minValue) - 20 + cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field'), 200) + // then we can type into the input + cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').type(`{selectall}{del}${lessThanMinValue}`) + cy.focused().blur() + cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').should('have.value', minValue) + }) + }) + + it('text field rounds input to nearest step', () => { + // eslint-disable-next-line promise/catch-or-return, promise/always-return + + cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-button-dynamic-step')) + + cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field'), 200) + // then we can type into the input + cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').type('{selectall}{del}27') + cy.focused().blur() + cy.get('#nrdb-ui-widget-dashboard-ui-slider-text-field').should('have.value', 28) + }) }) diff --git a/docs/nodes/widgets/ui-slider.md b/docs/nodes/widgets/ui-slider.md index bf21e270..1becd5a1 100644 --- a/docs/nodes/widgets/ui-slider.md +++ b/docs/nodes/widgets/ui-slider.md @@ -59,6 +59,9 @@ dynamic: Class: payload: msg.ui_update.class structure: ["String"] + Show Text Field: + payload: msg.ui_update.showTextField + structure: ["true | false"] --- diff --git a/nodes/widgets/locales/en-US/ui_slider.json b/nodes/widgets/locales/en-US/ui_slider.json new file mode 100644 index 00000000..f0483d8b --- /dev/null +++ b/nodes/widgets/locales/en-US/ui_slider.json @@ -0,0 +1,22 @@ +{ + "ui-slider": { + "label": { + "name": "Name", + "group": "Group", + "size": "Size", + "class": "Class", + "label": "Label", + "thumb": "Thumb", + "ticks": "Ticks", + "range": "Range", + "min": "min.", + "max": "max.", + "step": "step", + "icons": "Icons", + "color": "Color", + "outputs": "Output", + "show": "Show", + "textField": "Text Field" + } + } +} \ No newline at end of file diff --git a/nodes/widgets/ui_slider.html b/nodes/widgets/ui_slider.html index db5440b1..fa778ae8 100644 --- a/nodes/widgets/ui_slider.html +++ b/nodes/widgets/ui_slider.html @@ -38,7 +38,8 @@ iconAppend: { value: '' }, color: { value: '' }, colorTrack: { value: '' }, - colorThumb: { value: '' } + colorThumb: { value: '' }, + showTextField: { value: false } }, inputs: 1, outputs: 1, @@ -86,6 +87,12 @@ $('#node-input-showTicks').val('false') } + // handle no value for showTextField (from sliders created before this was a value) + if (!this.showTextField) { + this.showTextField = false + $('#node-input-showTextField').val(false) + } + // use jQuery UI tooltip to convert the plain old title attribute to a nice tooltip $('.ui-node-popover-title').tooltip({ show: { @@ -144,11 +151,16 @@