diff --git a/cypress/fixtures/flows/dashboard-text.json b/cypress/fixtures/flows/dashboard-text.json index 078a904a..f54a6a7b 100644 --- a/cypress/fixtures/flows/dashboard-text.json +++ b/cypress/fixtures/flows/dashboard-text.json @@ -403,6 +403,116 @@ ] ] }, + { + "id": "button-dynamic-label-no-payload", + "type": "ui-button", + "z": "node-red-tab-text", + "group": "869909cd180dde02", + "name": "Dynamic: No Payload", + "label": "Dynamic: No Payload", + "order": 7, + "width": 0, + "height": 0, + "emulateClick": false, + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "iconPosition": "left", + "payload": "", + "payloadType": "str", + "topic": "button-topic", + "topicType": "str", + "buttonColor": "", + "textColor": "", + "iconColor": "", + "enableClick": true, + "enablePointerdown": false, + "pointerdownPayload": "", + "pointerdownPayloadType": "str", + "enablePointerup": false, + "pointerupPayload": "", + "pointerupPayloadType": "str", + "x": 120, + "y": 320, + "wires": [ + [ + "df51827510c6dc80" + ] + ] + }, + { + "id": "df51827510c6dc80", + "type": "change", + "z": "node-red-tab-text", + "name": "", + "rules": [ + { + "t": "set", + "p": "ui_update.label", + "pt": "msg", + "to": "Dynamic Label-No Payload", + "tot": "str" + }, + { + "t": "delete", + "p": "payload", + "pt": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 360, + "y": 320, + "wires": [ + [ + "dashboard-ui-text-dynamic" + ] + ] + }, + { + "id": "button-inject-text-2", + "type": "ui-button", + "z": "node-red-tab-text", + "group": "869909cd180dde02", + "name": "", + "label": "Inject Text 2", + "order": 8, + "width": 0, + "height": 0, + "emulateClick": false, + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "iconPosition": "left", + "payload": "injected text", + "payloadType": "str", + "topic": "button-topic", + "topicType": "str", + "buttonColor": "", + "textColor": "", + "iconColor": "", + "enableClick": true, + "enablePointerdown": false, + "pointerdownPayload": "", + "pointerdownPayloadType": "str", + "enablePointerup": false, + "pointerupPayload": "", + "pointerupPayloadType": "str", + "x": 390, + "y": 360, + "wires": [ + [ + "dashboard-ui-text-dynamic" + ] + ] + }, { "id": "dashboard-test-text", "type": "ui-group", diff --git a/cypress/tests/widgets/text.spec.js b/cypress/tests/widgets/text.spec.js index 23de63be..eb9b9b7d 100644 --- a/cypress/tests/widgets/text.spec.js +++ b/cypress/tests/widgets/text.spec.js @@ -64,4 +64,14 @@ describe('Node-RED Dashboard 2.0 - Text - Dynamic Properties', () => { cy.clickAndWait(cy.get('#nrdb-ui-widget-button-dynamic-color')) cy.get('#nrdb-ui-widget-dashboard-ui-text-dynamic .nrdb-ui-text').should('have.css', 'color', 'rgb(255, 0, 0)') }) + + it('retains previous value on dynamic input without payload', () => { + cy.get('#nrdb-ui-widget-dashboard-ui-text-left').should('not.contain', 'injected text') + cy.clickAndWait(cy.get('#nrdb-ui-widget-button-inject-text-2')) + cy.get('#nrdb-ui-widget-dashboard-ui-text-dynamic').contains('injected text') + cy.get('#nrdb-ui-widget-dashboard-ui-text-dynamic').contains('Dynamic Label') + cy.clickAndWait(cy.get('#nrdb-ui-widget-button-dynamic-label-no-payload')) + cy.get('#nrdb-ui-widget-dashboard-ui-text-dynamic').contains('Dynamic Label-No Payload') + cy.get('#nrdb-ui-widget-dashboard-ui-text-dynamic').contains('injected text') + }) }) diff --git a/ui/src/widgets/ui-text/UIText.vue b/ui/src/widgets/ui-text/UIText.vue index 4ac61c58..f88d14a2 100644 --- a/ui/src/widgets/ui-text/UIText.vue +++ b/ui/src/widgets/ui-text/UIText.vue @@ -19,15 +19,15 @@ export default { props: { type: Object, default: () => ({}) }, state: { type: Object, default: () => ({}) } }, + data () { + return { + textValue: '' + } + }, computed: { ...mapState('data', ['messages', 'properties']), - value: function () { - const m = this.messages[this.id] || {} - if (Object.prototype.hasOwnProperty.call(m, 'payload')) { - // Sanetize the html to avoid XSS attacks - return DOMPurify.sanitize(m.payload) - } - return '' + value () { + return this.textValue }, label () { // Sanetize the html to avoid XSS attacks @@ -51,7 +51,7 @@ export default { } }, created () { - this.$dataTracker(this.id, null, null, this.onDynamicProperties) + this.$dataTracker(this.id, this.onInput, this.onLoad, this.onDynamicProperties) }, methods: { onDynamicProperties (msg) { @@ -64,6 +64,31 @@ export default { this.updateDynamicProperty('font', updates.font) this.updateDynamicProperty('fontSize', updates.fontSize) this.updateDynamicProperty('color', updates.color) + }, + onInput (msg) { + // update our vuex store with the value retrieved from Node-RED + this.$store.commit('data/bind', { + widgetId: this.id, + msg + }) + // make sure our v-model is updated to reflect the value from Node-RED + if (Object.prototype.hasOwnProperty.call(msg, 'payload')) { + // Sanitize the HTML to avoid XSS attacks + this.textValue = DOMPurify.sanitize(msg.payload) + } + }, + onLoad (msg) { + if (msg) { + // update vuex store to reflect server-state + this.$store.commit('data/bind', { + widgetId: this.id, + msg + }) + if (Object.prototype.hasOwnProperty.call(msg, 'payload')) { + // Sanitize the HTML to avoid XSS attacks + this.textValue = DOMPurify.sanitize(msg.payload) + } + } } } }